diff --git a/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php b/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php index 43e84ed1795f5..aa1edd576f6aa 100644 --- a/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php +++ b/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php @@ -10,7 +10,6 @@ namespace OCA\FederatedFileSharing\Migration; use Closure; -use Doctrine\DBAL\Types\Type; use OCP\DB\ISchemaWrapper; use OCP\DB\Types; use OCP\IDBConnection; @@ -32,10 +31,10 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if ($schema->hasTable('federated_reshares')) { $table = $schema->getTable('federated_reshares'); $remoteIdColumn = $table->getColumn('remote_id'); - if ($remoteIdColumn && Type::lookupName($remoteIdColumn->getType()) !== Types::STRING) { + if ($remoteIdColumn->getType()->getName() !== Types::STRING) { $remoteIdColumn->setNotnull(false); - $remoteIdColumn->setType(Type::getType(Types::STRING)); - $remoteIdColumn->setOptions(['length' => 255]); + $remoteIdColumn->setType(Types::STRING); + $remoteIdColumn->setLength(255); $remoteIdColumn->setDefault(''); return $schema; } diff --git a/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php b/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php index 78035d9ccda9c..4800e98eca549 100644 --- a/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php +++ b/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php @@ -10,7 +10,6 @@ namespace OCA\Files_Sharing\Migration; use Closure; -use Doctrine\DBAL\Types\Type; use OCP\DB\ISchemaWrapper; use OCP\DB\Types; use OCP\IDBConnection; @@ -91,10 +90,10 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt } else { $table = $schema->getTable('share_external'); $remoteIdColumn = $table->getColumn('remote_id'); - if ($remoteIdColumn && Type::lookupName($remoteIdColumn->getType()) !== Types::STRING) { + if ($remoteIdColumn->getType()->getName() !== Types::STRING) { $remoteIdColumn->setNotnull(false); - $remoteIdColumn->setType(Type::getType(Types::STRING)); - $remoteIdColumn->setOptions(['length' => 255]); + $remoteIdColumn->setType(Types::STRING); + $remoteIdColumn->setLength(255); $remoteIdColumn->setDefault(''); } if (!$table->hasColumn('parent')) { diff --git a/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php b/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php index dc73f2fd3932c..554c6915b2138 100644 --- a/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php +++ b/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php @@ -9,10 +9,10 @@ namespace OCA\Settings\SetupChecks; -use Doctrine\DBAL\Types\BigIntType; use OC\Core\Command\Db\ConvertFilecacheBigInt; use OC\DB\Connection; use OC\DB\SchemaWrapper; +use OCP\DB\Types; use OCP\EventDispatcher\IEventDispatcher; use OCP\IDBConnection; use OCP\IL10N; @@ -57,7 +57,7 @@ protected function getBigIntConversionPendingColumns(): array { $column = $table->getColumn($columnName); $isAutoIncrement = $column->getAutoincrement(); $isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement; - if (!($column->getType() instanceof BigIntType) && !$isAutoIncrementOnSqlite) { + if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) { $pendingColumns[] = $tableName . '.' . $columnName; } } diff --git a/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170919123342.php b/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170919123342.php index c96263360f6df..a36397625fb06 100644 --- a/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170919123342.php +++ b/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170919123342.php @@ -9,9 +9,8 @@ namespace OCA\TwoFactorBackupCodes\Migration; -use Doctrine\DBAL\Types\Type; -use Doctrine\DBAL\Types\Types; use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; @@ -35,8 +34,8 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op $column = $table->getColumn('used'); if ($column->getType()->getName() !== Types::SMALLINT) { - $column->setType(Type::getType(Types::SMALLINT)); - $column->setOptions(['length' => 6]); + $column->setType(Types::SMALLINT); + $column->setLength(6); } return $schema; diff --git a/apps/workflowengine/lib/Migration/Version2000Date20190808074233.php b/apps/workflowengine/lib/Migration/Version2000Date20190808074233.php index 1d6c0ae85687b..bf037aa67e682 100644 --- a/apps/workflowengine/lib/Migration/Version2000Date20190808074233.php +++ b/apps/workflowengine/lib/Migration/Version2000Date20190808074233.php @@ -10,9 +10,9 @@ namespace OCA\WorkflowEngine\Migration; use Closure; -use Doctrine\DBAL\Schema\Table; use OCA\WorkflowEngine\Entity\File; use OCP\DB\ISchemaWrapper; +use OCP\DB\Schema\ITable; use OCP\DB\Types; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; @@ -118,7 +118,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt return $schema; } - protected function ensureEntityColumns(Table $table) { + protected function ensureEntityColumns(ITable $table): void { if (!$table->hasColumn('entity')) { $table->addColumn('entity', Types::STRING, [ 'notnull' => true, diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 8fbfd584765b4..0aac69f0d8ad1 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -2669,11 +2669,6 @@ - - - - - diff --git a/core/Command/Db/ConvertFilecacheBigInt.php b/core/Command/Db/ConvertFilecacheBigInt.php index 5d55cdb314ea8..2547a9f64e0e2 100644 --- a/core/Command/Db/ConvertFilecacheBigInt.php +++ b/core/Command/Db/ConvertFilecacheBigInt.php @@ -7,7 +7,6 @@ namespace OC\Core\Command\Db; -use Doctrine\DBAL\Types\Type; use OC\DB\Connection; use OC\DB\SchemaWrapper; use OCP\DB\Types; @@ -73,9 +72,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $column = $table->getColumn($columnName); $isAutoIncrement = $column->getAutoincrement(); $isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement; - if (Type::lookupName($column->getType()) !== Types::BIGINT && !$isAutoIncrementOnSqlite) { - $column->setType(Type::getType(Types::BIGINT)); - $column->setOptions(['length' => 20]); + if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) { + $column->setType(Types::BIGINT); + $column->setLength(20); $updates[] = '* ' . $tableName . '.' . $columnName; } diff --git a/core/Migrations/Version34000Date20260318095645.php b/core/Migrations/Version34000Date20260318095645.php index 8be6dfc912277..103cb68eed072 100644 --- a/core/Migrations/Version34000Date20260318095645.php +++ b/core/Migrations/Version34000Date20260318095645.php @@ -10,7 +10,6 @@ namespace OC\Core\Migrations; use Closure; -use Doctrine\DBAL\Types\Type; use OCP\DB\ISchemaWrapper; use OCP\DB\Types; use OCP\Migration\Attributes\ColumnType; @@ -30,8 +29,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table = $schema->getTable('jobs'); $argumentColumn = $table->getColumn('argument'); - if ($argumentColumn->getType() !== Type::getType(Types::TEXT)) { - $argumentColumn->setType(Type::getType(Types::TEXT)); + if ($argumentColumn->getType()->getName() !== Types::TEXT) { + $argumentColumn->setType(Types::TEXT); return $schema; } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 6472ccd5e2050..9ee0b66bdc418 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -369,6 +369,11 @@ 'OCP\\DB\\QueryBuilder\\IQueryFunction' => $baseDir . '/lib/public/DB/QueryBuilder/IQueryFunction.php', 'OCP\\DB\\QueryBuilder\\ITypedQueryBuilder' => $baseDir . '/lib/public/DB/QueryBuilder/ITypedQueryBuilder.php', 'OCP\\DB\\QueryBuilder\\Sharded\\IShardMapper' => $baseDir . '/lib/public/DB/QueryBuilder/Sharded/IShardMapper.php', + 'OCP\\DB\\Schema\\IColumn' => $baseDir . '/lib/public/DB/Schema/IColumn.php', + 'OCP\\DB\\Schema\\IIndex' => $baseDir . '/lib/public/DB/Schema/IIndex.php', + 'OCP\\DB\\Schema\\ITable' => $baseDir . '/lib/public/DB/Schema/ITable.php', + 'OCP\\DB\\Schema\\IType' => $baseDir . '/lib/public/DB/Schema/IType.php', + 'OCP\\DB\\Schema\\SchemaException' => $baseDir . '/lib/public/DB/Schema/SchemaException.php', 'OCP\\DB\\Types' => $baseDir . '/lib/public/DB/Types.php', 'OCP\\Dashboard\\IAPIWidget' => $baseDir . '/lib/public/Dashboard/IAPIWidget.php', 'OCP\\Dashboard\\IAPIWidgetV2' => $baseDir . '/lib/public/Dashboard/IAPIWidgetV2.php', @@ -1774,6 +1779,10 @@ 'OC\\DB\\SQLiteMigrator' => $baseDir . '/lib/private/DB/SQLiteMigrator.php', 'OC\\DB\\SQLiteSessionInit' => $baseDir . '/lib/private/DB/SQLiteSessionInit.php', 'OC\\DB\\SchemaWrapper' => $baseDir . '/lib/private/DB/SchemaWrapper.php', + 'OC\\DB\\Schema\\Column' => $baseDir . '/lib/private/DB/Schema/Column.php', + 'OC\\DB\\Schema\\Index' => $baseDir . '/lib/private/DB/Schema/Index.php', + 'OC\\DB\\Schema\\Table' => $baseDir . '/lib/private/DB/Schema/Table.php', + 'OC\\DB\\Schema\\Type' => $baseDir . '/lib/private/DB/Schema/Type.php', 'OC\\DB\\SetTransactionIsolationLevel' => $baseDir . '/lib/private/DB/SetTransactionIsolationLevel.php', 'OC\\Dashboard\\Manager' => $baseDir . '/lib/private/Dashboard/Manager.php', 'OC\\DatabaseException' => $baseDir . '/lib/private/DatabaseException.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index eb51034b2dff1..1b0d9f24271fa 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -410,6 +410,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\DB\\QueryBuilder\\IQueryFunction' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IQueryFunction.php', 'OCP\\DB\\QueryBuilder\\ITypedQueryBuilder' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/ITypedQueryBuilder.php', 'OCP\\DB\\QueryBuilder\\Sharded\\IShardMapper' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/Sharded/IShardMapper.php', + 'OCP\\DB\\Schema\\IColumn' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/IColumn.php', + 'OCP\\DB\\Schema\\IIndex' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/IIndex.php', + 'OCP\\DB\\Schema\\ITable' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/ITable.php', + 'OCP\\DB\\Schema\\IType' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/IType.php', + 'OCP\\DB\\Schema\\SchemaException' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/SchemaException.php', 'OCP\\DB\\Types' => __DIR__ . '/../../..' . '/lib/public/DB/Types.php', 'OCP\\Dashboard\\IAPIWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IAPIWidget.php', 'OCP\\Dashboard\\IAPIWidgetV2' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IAPIWidgetV2.php', @@ -1815,6 +1820,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\DB\\SQLiteMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteMigrator.php', 'OC\\DB\\SQLiteSessionInit' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteSessionInit.php', 'OC\\DB\\SchemaWrapper' => __DIR__ . '/../../..' . '/lib/private/DB/SchemaWrapper.php', + 'OC\\DB\\Schema\\Column' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Column.php', + 'OC\\DB\\Schema\\Index' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Index.php', + 'OC\\DB\\Schema\\Table' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Table.php', + 'OC\\DB\\Schema\\Type' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Type.php', 'OC\\DB\\SetTransactionIsolationLevel' => __DIR__ . '/../../..' . '/lib/private/DB/SetTransactionIsolationLevel.php', 'OC\\Dashboard\\Manager' => __DIR__ . '/../../..' . '/lib/private/Dashboard/Manager.php', 'OC\\DatabaseException' => __DIR__ . '/../../..' . '/lib/private/DatabaseException.php', diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 12bc7d975ef0d..761fa5398b69e 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -10,7 +10,6 @@ use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; -use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Type; @@ -18,6 +17,7 @@ use OC\Migration\SimpleOutput; use OCP\App\IAppManager; use OCP\DB\ISchemaWrapper; +use OCP\DB\Schema\SchemaException; use OCP\DB\Types; use OCP\IConfig; use OCP\IDBConnection; @@ -130,7 +130,7 @@ private function createMigrationTable(): bool { $column = $table->getColumn('version'); $schemaMismatch = $column->getLength() !== 255; } - } catch (SchemaException $e) { + } catch (SchemaException|\Doctrine\DBAL\Schema\SchemaException) { // One of the columns is missing $schemaMismatch = true; } @@ -147,7 +147,7 @@ private function createMigrationTable(): bool { // Recreate the schema wrapper after dropping the table. $schema = new SchemaWrapper($this->connection); - } catch (SchemaException $e) { + } catch (SchemaException|\Doctrine\DBAL\Schema\SchemaException) { // The table does not exist; it will be created below. } @@ -550,7 +550,7 @@ public function ensureNamingConstraints(Schema $sourceSchema, Schema $targetSche foreach ($targetSchema->getTables() as $table) { try { $sourceTable = $sourceSchema->getTable($table->getName()); - } catch (SchemaException $e) { + } catch (\Doctrine\DBAL\Schema\SchemaException $e) { // we only validate new tables if (\strlen($table->getName()) + $prefixLength > $MAX_NAME_LENGTH) { throw new \InvalidArgumentException('Table name "' . $table->getName() . '" exceeds the maximum length of ' . $MAX_NAME_LENGTH); @@ -643,7 +643,7 @@ public function ensureOracleConstraints(Schema $sourceSchema, Schema $targetSche foreach ($targetSchema->getTables() as $table) { try { $sourceTable = $sourceSchema->getTable($table->getName()); - } catch (SchemaException $e) { + } catch (\Doctrine\DBAL\Schema\SchemaException|SchemaException) { $sourceTable = null; } diff --git a/lib/private/DB/Schema/Column.php b/lib/private/DB/Schema/Column.php new file mode 100644 index 0000000000000..1d9a126240615 --- /dev/null +++ b/lib/private/DB/Schema/Column.php @@ -0,0 +1,152 @@ +column; + } + + #[\Override] + public function setType(string|IType|DBALType $type): self { + if ($type instanceof IType) { + $type = $type->getName(); + } + + $this->column->setType($type instanceof DBALType ? $type : DBALType::getType($type)); + + return $this; + } + + #[\Override] + public function setLength(?int $length): self { + $this->column->setLength($length); + + return $this; + } + + #[\Override] + public function setPrecision(int $precision): self { + $this->column->setPrecision($precision); + + return $this; + } + + #[\Override] + public function setScale(int $scale): self { + $this->column->setScale($scale); + + return $this; + } + + #[\Override] + public function setUnsigned(bool $unsigned): self { + $this->column->setUnsigned($unsigned); + + return $this; + } + + #[\Override] + public function setFixed(bool $fixed): self { + $this->column->setFixed($fixed); + + return $this; + } + + #[\Override] + public function setNotnull(bool $notnull): self { + $this->column->setNotnull($notnull); + + return $this; + } + + #[\Override] + public function setDefault(mixed $default): self { + $this->column->setDefault($default); + + return $this; + } + + #[\Override] + public function getType(): IType { + return new Type($this->column->getType()); + } + + #[\Override] + public function getLength(): ?int { + return $this->column->getLength(); + } + + #[\Override] + public function getPrecision(): int { + return $this->column->getPrecision(); + } + + #[\Override] + public function getScale(): int { + return $this->column->getScale(); + } + + #[\Override] + public function getUnsigned(): bool { + return $this->column->getUnsigned(); + } + + #[\Override] + public function getFixed(): bool { + return $this->column->getFixed(); + } + + #[\Override] + public function getNotnull(): bool { + return $this->column->getNotnull(); + } + + #[\Override] + public function getDefault(): mixed { + return $this->column->getDefault(); + } + + #[\Override] + public function getAutoincrement(): bool { + return $this->column->getAutoincrement(); + } + + /** + * Forwards any method not declared on IColumn to the wrapped Doctrine + * DBAL column, e.g. read-only accessors like `getName()` or + * `getAutoincrement()` that are not part of the public API. + */ + public function __call(string $name, array $arguments): mixed { + try { + return $this->column->$name(...$arguments); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/lib/private/DB/Schema/Index.php b/lib/private/DB/Schema/Index.php new file mode 100644 index 0000000000000..dc3ea40328ede --- /dev/null +++ b/lib/private/DB/Schema/Index.php @@ -0,0 +1,64 @@ +index; + } + + #[\Override] + public function getName(): string { + return $this->index->getName(); + } + + #[\Override] + public function getColumns(): array { + return array_values($this->index->getColumns()); + } + + #[\Override] + public function isUnique(): bool { + return $this->index->isUnique(); + } + + #[\Override] + public function isPrimary(): bool { + return $this->index->isPrimary(); + } + + #[\Override] + public function isSimpleIndex(): bool { + return $this->index->isSimpleIndex(); + } + + /** + * Forwards any method not declared on IIndex to the wrapped Doctrine + * DBAL index, e.g. mutators like `addFlag()` or `removeFlag()` that are + * not part of the public API. + */ + public function __call(string $name, array $arguments): mixed { + return $this->index->$name(...$arguments); + } +} diff --git a/lib/private/DB/Schema/Table.php b/lib/private/DB/Schema/Table.php new file mode 100644 index 0000000000000..25c3c7f6413c7 --- /dev/null +++ b/lib/private/DB/Schema/Table.php @@ -0,0 +1,241 @@ +table; + } + + #[\Override] + public function getName(): string { + return $this->table->getName(); + } + + #[\Override] + public function setPrimaryKey(array $columnNames, string|false $indexName = false): self { + try { + $this->table->setPrimaryKey($columnNames, $indexName); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []): self { + try { + $this->table->addIndex($columnNames, $indexName, $flags, $options); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function addUniqueConstraint( + array $columnNames, + ?string $indexName = null, + array $flags = [], + array $options = [], + ): self { + try { + $this->table->addUniqueConstraint($columnNames, $indexName, $flags, $options); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function dropPrimaryKey(): self { + try { + $this->table->dropPrimaryKey(); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function getPrimaryKey(): ?IIndex { + $primaryKey = $this->table->getPrimaryKey(); + + return $primaryKey !== null ? new Index($primaryKey) : null; + } + + #[\Override] + public function dropIndex(string $name): self { + try { + $this->table->dropIndex($name); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function hasIndex(string $name): bool { + return $this->table->hasIndex($name); + } + + #[\Override] + public function addUniqueIndex(array $columnNames, ?string $indexName = null, array $options = []): self { + try { + $this->table->addUniqueIndex($columnNames, $indexName, $options); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function renameIndex(string $oldName, ?string $newName = null): self { + try { + $this->table->renameIndex($oldName, $newName); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function addColumn(string $name, string $typeName, array $options = []): IColumn { + try { + return new Column($this->table->addColumn($name, $typeName, $options)); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + } + + #[\Override] + public function modifyColumn(string $name, array $options): self { + try { + $this->table->modifyColumn($name, $options); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function dropColumn(string $name): self { + $this->table->dropColumn($name); + + return $this; + } + + #[\Override] + public function addForeignKeyConstraint( + ITable|string $foreignTable, + array $localColumnNames, + array $foreignColumnNames, + array $options = [], + ?string $name = null, + ): self { + try { + $this->table->addForeignKeyConstraint( + $foreignTable instanceof self ? $foreignTable->getWrappedTable() : $foreignTable, + $localColumnNames, + $foreignColumnNames, + $options, + $name, + ); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + + return $this; + } + + #[\Override] + public function hasForeignKey(string $name): bool { + return $this->table->hasForeignKey($name); + } + + #[\Override] + public function removeForeignKey(string $name): void { + try { + $this->table->removeForeignKey($name); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Forwards any method not declared on ITable to the wrapped Doctrine + * DBAL table, e.g. read-only accessors that are not part of the public API. + */ + public function __call(string $name, array $arguments): mixed { + try { + return $this->table->$name(...$arguments); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + } + + #[\Override] + public function hasColumn(string $string): bool { + return $this->table->hasColumn($string); + } + + #[\Override] + public function getColumn(string $name): IColumn { + try { + return new Column($this->table->getColumn($name)); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } + } + + #[\Override] + public function getColumns(): array { + return array_values(array_map( + static fn (DBALColumn $column): IColumn => new Column($column), + $this->table->getColumns(), + )); + } + + #[\Override] + public function getIndexes(): array { + return array_values(array_map( + static fn (DBALIndex $index): IIndex => new Index($index), + $this->table->getIndexes(), + )); + } +} diff --git a/lib/private/DB/Schema/Type.php b/lib/private/DB/Schema/Type.php new file mode 100644 index 0000000000000..670f9d2ff92e0 --- /dev/null +++ b/lib/private/DB/Schema/Type.php @@ -0,0 +1,39 @@ +getType()->getName()` (the Doctrine DBAL API) working, without + * committing to the rest of Doctrine's Type API as public API. + */ +class Type implements IType { + public function __construct( + private DBALType $type, + ) { + } + + /** + * Returns the wrapped Doctrine DBAL type. + */ + public function getWrappedType(): DBALType { + return $this->type; + } + + #[\Override] + public function getName(): string { + return $this->type->getName(); + } +} diff --git a/lib/private/DB/SchemaWrapper.php b/lib/private/DB/SchemaWrapper.php index 103bcb25cccaf..429d3e5775ade 100644 --- a/lib/private/DB/SchemaWrapper.php +++ b/lib/private/DB/SchemaWrapper.php @@ -1,5 +1,7 @@ schema; } @@ -46,14 +53,9 @@ public function performDropTableCalls(): void { } } - /** - * Gets all table names - * - * @return array - */ #[\Override] - public function getTableNamesWithoutPrefix() { - $tableNames = $this->schema->getTableNames(); + public function getTableNamesWithoutPrefix(): array { + $tableNames = $this->getTableNames(); return array_map(function ($tableName) { if (str_starts_with($tableName, $this->connection->getPrefix())) { return substr($tableName, strlen($this->connection->getPrefix())); @@ -65,80 +67,52 @@ public function getTableNamesWithoutPrefix() { // Overwritten methods - /** - * @return array - */ #[\Override] - public function getTableNames() { - return $this->schema->getTableNames(); + public function getTableNames(): array { + return array_values(array_map(fn (DBALTable $table): string => $table->getName(), $this->schema->getTables())); } - /** - * @param string $tableName - * - * @return \Doctrine\DBAL\Schema\Table - * @throws \Doctrine\DBAL\Schema\SchemaException - */ #[\Override] - public function getTable($tableName) { - return $this->schema->getTable($this->connection->getPrefix() . $tableName); + public function getTable(string $tableName): ITable { + try { + return new Table($this->schema->getTable($this->connection->getPrefix() . $tableName)); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } } /** * Does this schema have a table with the given name? - * - * @param string $tableName - * - * @return boolean */ #[\Override] - public function hasTable($tableName) { + public function hasTable(string $tableName): bool { return $this->schema->hasTable($this->connection->getPrefix() . $tableName); } - /** - * Creates a new table. - * - * @param string $tableName - * @return \Doctrine\DBAL\Schema\Table - */ #[\Override] - public function createTable($tableName) { + public function createTable(string $tableName): ITable { unset($this->tablesToDelete[$tableName]); - return $this->schema->createTable($this->connection->getPrefix() . $tableName); + try { + return new Table($this->schema->createTable($this->connection->getPrefix() . $tableName)); + } catch (DBALSchemaException $e) { + throw new SchemaException($e->getMessage(), $e->getCode(), $e); + } } - /** - * Drops a table from the schema. - * - * @param string $tableName - * @return \Doctrine\DBAL\Schema\Schema - */ #[\Override] - public function dropTable($tableName) { + public function dropTable(string $tableName): self { $this->tablesToDelete[$tableName] = true; - return $this->schema->dropTable($this->connection->getPrefix() . $tableName); + $this->schema->dropTable($this->connection->getPrefix() . $tableName); + return $this; } - /** - * Gets all tables of this schema. - * - * @return \Doctrine\DBAL\Schema\Table[] - */ #[\Override] - public function getTables() { - return $this->schema->getTables(); + public function getTables(): array { + return array_values(array_map(fn (DBALTable $table): ITable => new Table($table), $this->schema->getTables())); } - /** - * Gets the DatabasePlatform for the database. - * - * @return AbstractPlatform - * - * @throws Exception - */ #[\Override] - public function getDatabasePlatform() { + public function getDatabasePlatform(): AbstractPlatform { return $this->connection->getDatabasePlatform(); } diff --git a/lib/private/Repair/Owncloud/MigratePropertiesTable.php b/lib/private/Repair/Owncloud/MigratePropertiesTable.php index 09fe5fddb8905..c172db9c7ac5a 100644 --- a/lib/private/Repair/Owncloud/MigratePropertiesTable.php +++ b/lib/private/Repair/Owncloud/MigratePropertiesTable.php @@ -9,10 +9,9 @@ namespace OC\Repair\Owncloud; -use Doctrine\DBAL\Types\StringType; -use Doctrine\DBAL\Types\Type; use OC\DB\Connection; use OC\DB\SchemaWrapper; +use OCP\DB\Types; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; @@ -39,8 +38,8 @@ public function run(IOutput $output): void { $output->info('Update the oc_properties table schema.'); $table = $schema->getTable('oc_properties'); $column = $table->getColumn('propertyvalue'); - if ($column->getType() instanceof StringType) { - $column->setType(Type::getType('text')); + if ($column->getType()->getName() === Types::STRING) { + $column->setType(Types::TEXT); $column->setLength(null); } diff --git a/lib/public/DB/ISchemaWrapper.php b/lib/public/DB/ISchemaWrapper.php index faefa988e4d68..cc94f1035b34d 100644 --- a/lib/public/DB/ISchemaWrapper.php +++ b/lib/public/DB/ISchemaWrapper.php @@ -9,6 +9,8 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; +use OCP\AppFramework\Attribute\Consumable; +use OCP\DB\Schema\ITable; /** * This interface allows to get information about the database schema. @@ -20,67 +22,62 @@ * * @since 13.0.0 */ +#[Consumable(since: '13.0.0')] interface ISchemaWrapper { /** - * @param string $tableName - * - * @return \Doctrine\DBAL\Schema\Table - * @throws \Doctrine\DBAL\Schema\SchemaException + * @throws Schema\SchemaException * @since 13.0.0 */ - public function getTable($tableName); + public function getTable(string $tableName): ITable; /** * Does this schema have a table with the given name? * * @param string $tableName Prefix is automatically prepended * - * @return boolean * @since 13.0.0 */ - public function hasTable($tableName); + public function hasTable(string $tableName): bool; /** * Creates a new table. * * @param string $tableName Prefix is automatically prepended - * @return \Doctrine\DBAL\Schema\Table * @since 13.0.0 */ - public function createTable($tableName); + public function createTable(string $tableName): ITable; /** * Drops a table from the schema. * * @param string $tableName Prefix is automatically prepended - * @return \Doctrine\DBAL\Schema\Schema * @since 13.0.0 */ - public function dropTable($tableName); + public function dropTable(string $tableName): self; /** * Gets all tables of this schema. * - * @return \Doctrine\DBAL\Schema\Table[] + * @return list * @since 13.0.0 */ - public function getTables(); + public function getTables(): array; /** * Gets all table names, prefixed with table prefix * - * @return array + * @return list * @since 13.0.0 */ - public function getTableNames(); + public function getTableNames(): array; /** * Gets all table names * - * @return array + * @return list * @since 13.0.0 */ - public function getTableNamesWithoutPrefix(); + public function getTableNamesWithoutPrefix(): array; /** * Gets the DatabasePlatform for the database. @@ -90,7 +87,7 @@ public function getTableNamesWithoutPrefix(); * @throws Exception * @since 23.0.0 */ - public function getDatabasePlatform(); + public function getDatabasePlatform(): AbstractPlatform; /** * Drop autoincrement from an existing table of the database. diff --git a/lib/public/DB/Schema/IColumn.php b/lib/public/DB/Schema/IColumn.php new file mode 100644 index 0000000000000..b9d284fd0c59a --- /dev/null +++ b/lib/public/DB/Schema/IColumn.php @@ -0,0 +1,119 @@ + + * @since 35.0.0 + */ + public function getColumns(): array; + + /** + * Returns whether this index is a unique index. + * + * @since 35.0.0 + */ + public function isUnique(): bool; + + /** + * Returns whether this index is the primary key. + * + * @since 35.0.0 + */ + public function isPrimary(): bool; + + /** + * Returns whether this index is neither unique nor the primary key. + * + * @since 35.0.0 + */ + public function isSimpleIndex(): bool; +} diff --git a/lib/public/DB/Schema/ITable.php b/lib/public/DB/Schema/ITable.php new file mode 100644 index 0000000000000..d388129058644 --- /dev/null +++ b/lib/public/DB/Schema/ITable.php @@ -0,0 +1,229 @@ + $columnNames + * @param string|false $indexName + * + * @throws SchemaException + * @since 35.0.0 + */ + public function setPrimaryKey(array $columnNames, string|false $indexName = false): self; + + /** + * @param list $columnNames + * @param list $flags + * @param array $options + * + * @throws SchemaException + * @since 35.0.0 + */ + public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []): self; + + /** + * @param list $columnNames + * @param list $flags + * @param array $options + * @since 35.0.0 + */ + public function addUniqueConstraint( + array $columnNames, + ?string $indexName = null, + array $flags = [], + array $options = [], + ): self; + + /** + * Drops the primary key from this table. + * + * @throws SchemaException + * @since 35.0.0 + */ + public function dropPrimaryKey(): self; + + /** + * Returns the primary key, or null if this table has no primary key. + * + * @since 35.0.0 + */ + public function getPrimaryKey(): ?IIndex; + + /** + * Drops an index from this table. + * + * @param string $name The index name. + * + * @throws SchemaException If the index does not exist. + * @since 35.0.0 + */ + public function dropIndex(string $name): self; + + /** + * Returns whether this table has an index with the given name. + * + * @param string $name The index name. + * @since 35.0.0 + */ + public function hasIndex(string $name): bool; + + /** + * @param list $columnNames + * @param string|null $indexName + * @param array $options + * + * @throws SchemaException + * @since 35.0.0 + */ + public function addUniqueIndex(array $columnNames, ?string $indexName = null, array $options = []): self; + + /** + * Renames an index. + * + * @param string $oldName The name of the index to rename from. + * @param string|null $newName The name of the index to rename to. + * If null is given, the index name will be auto-generated. + * + * @return self This table instance. + * + * @throws SchemaException If no index exists for the given current name + * or if an index with the given new name already exists on this table. + * @since 35.0.0 + */ + public function renameIndex(string $oldName, ?string $newName = null): self; + + /** + * @param string $name + * @param string $typeName + * @param array{ + * notnull?: bool, + * length?: ?int, + * default?: ?scalar, + * unsigned?: bool, + * autoincrement?: bool, + * fixed?: bool, + * } $options + * + * @throws SchemaException + * @since 35.0.0 + */ + public function addColumn(string $name, string $typeName, array $options = []): IColumn; + + /** + * @param string $name + * @param array{ + * notnull?: bool, + * length?: ?int, + * default?: ?scalar, + * unsigned?: bool, + * autoincrement?: bool, + * fixed?: bool, + * } $options + * + * @throws SchemaException + * @since 35.0.0 + */ + public function modifyColumn(string $name, array $options): self; + + /** + * Drops a Column from the Table. + * @since 35.0.0 + */ + public function dropColumn(string $name): self; + + /** + * Returns whether this table has a Column with the given name. + * + * @param string $name The column name. + * @since 35.0.0 + */ + public function hasColumn(string $string): bool; + + /** + * Returns the Column with the given name. + * + * @param string $name The column name. + * + * @throws SchemaException If the column does not exist. + * @since 35.0.0 + */ + public function getColumn(string $name): IColumn; + + /** + * Returns all columns of this table. + * + * @return list + * @since 35.0.0 + */ + public function getColumns(): array; + + /** + * Returns all indexes of this table. + * + * @return list + * @since 35.0.0 + */ + public function getIndexes(): array; + + /** + * Adds a foreign key constraint. + * + * Name is inferred from the local columns. + * + * @param ITable|string $foreignTable Table schema instance or table name + * @param list $localColumnNames + * @param list $foreignColumnNames + * @param array $options + * + * @throws SchemaException + * @since 35.0.0 + */ + public function addForeignKeyConstraint( + ITable|string $foreignTable, + array $localColumnNames, + array $foreignColumnNames, + array $options = [], + ?string $name = null, + ): self; + + /** + * Returns whether this table has a foreign key constraint with the given name. + * @since 35.0.0 + */ + public function hasForeignKey(string $name): bool; + + /** + * Removes the foreign key constraint with the given name. + * + * @param string $name The constraint name. + * + * @throws SchemaException + * @since 35.0.0 + */ + public function removeForeignKey(string $name): void; +} diff --git a/lib/public/DB/Schema/IType.php b/lib/public/DB/Schema/IType.php new file mode 100644 index 0000000000000..241ee9c4465e6 --- /dev/null +++ b/lib/public/DB/Schema/IType.php @@ -0,0 +1,24 @@ +getColumn($columnName); - if (Type::lookupName($column->getType()) !== Types::BIGINT) { - $column->setType(Type::getType(Types::BIGINT)); - $column->setOptions(['length' => 20]); + if ($column->getType()->getName() !== Types::BIGINT) { + $column->setType(Types::BIGINT); + $column->setLength(20); } } }