Skip to content

Commit 9517b0f

Browse files
committed
feat(api): Add public OCP api for DB schema management
Same as the DBAL one aside that Column::getType returns a string instead of a DBAL Type. Column::setType support both a DBAL Type and a string. Signed-off-by: Carl Schwan <carl@carlschwan.eu>
1 parent 0556391 commit 9517b0f

14 files changed

Lines changed: 1073 additions & 81 deletions

File tree

lib/composer/composer/autoload_classmap.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,11 @@
369369
'OCP\\DB\\QueryBuilder\\IQueryFunction' => $baseDir . '/lib/public/DB/QueryBuilder/IQueryFunction.php',
370370
'OCP\\DB\\QueryBuilder\\ITypedQueryBuilder' => $baseDir . '/lib/public/DB/QueryBuilder/ITypedQueryBuilder.php',
371371
'OCP\\DB\\QueryBuilder\\Sharded\\IShardMapper' => $baseDir . '/lib/public/DB/QueryBuilder/Sharded/IShardMapper.php',
372+
'OCP\\DB\\Schema\\IColumn' => $baseDir . '/lib/public/DB/Schema/IColumn.php',
373+
'OCP\\DB\\Schema\\IIndex' => $baseDir . '/lib/public/DB/Schema/IIndex.php',
374+
'OCP\\DB\\Schema\\ITable' => $baseDir . '/lib/public/DB/Schema/ITable.php',
375+
'OCP\\DB\\Schema\\IType' => $baseDir . '/lib/public/DB/Schema/IType.php',
376+
'OCP\\DB\\Schema\\SchemaException' => $baseDir . '/lib/public/DB/Schema/SchemaException.php',
372377
'OCP\\DB\\Types' => $baseDir . '/lib/public/DB/Types.php',
373378
'OCP\\Dashboard\\IAPIWidget' => $baseDir . '/lib/public/Dashboard/IAPIWidget.php',
374379
'OCP\\Dashboard\\IAPIWidgetV2' => $baseDir . '/lib/public/Dashboard/IAPIWidgetV2.php',
@@ -1774,6 +1779,10 @@
17741779
'OC\\DB\\SQLiteMigrator' => $baseDir . '/lib/private/DB/SQLiteMigrator.php',
17751780
'OC\\DB\\SQLiteSessionInit' => $baseDir . '/lib/private/DB/SQLiteSessionInit.php',
17761781
'OC\\DB\\SchemaWrapper' => $baseDir . '/lib/private/DB/SchemaWrapper.php',
1782+
'OC\\DB\\Schema\\Column' => $baseDir . '/lib/private/DB/Schema/Column.php',
1783+
'OC\\DB\\Schema\\Index' => $baseDir . '/lib/private/DB/Schema/Index.php',
1784+
'OC\\DB\\Schema\\Table' => $baseDir . '/lib/private/DB/Schema/Table.php',
1785+
'OC\\DB\\Schema\\Type' => $baseDir . '/lib/private/DB/Schema/Type.php',
17771786
'OC\\DB\\SetTransactionIsolationLevel' => $baseDir . '/lib/private/DB/SetTransactionIsolationLevel.php',
17781787
'OC\\Dashboard\\Manager' => $baseDir . '/lib/private/Dashboard/Manager.php',
17791788
'OC\\DatabaseException' => $baseDir . '/lib/private/DatabaseException.php',

lib/composer/composer/autoload_static.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
410410
'OCP\\DB\\QueryBuilder\\IQueryFunction' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IQueryFunction.php',
411411
'OCP\\DB\\QueryBuilder\\ITypedQueryBuilder' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/ITypedQueryBuilder.php',
412412
'OCP\\DB\\QueryBuilder\\Sharded\\IShardMapper' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/Sharded/IShardMapper.php',
413+
'OCP\\DB\\Schema\\IColumn' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/IColumn.php',
414+
'OCP\\DB\\Schema\\IIndex' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/IIndex.php',
415+
'OCP\\DB\\Schema\\ITable' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/ITable.php',
416+
'OCP\\DB\\Schema\\IType' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/IType.php',
417+
'OCP\\DB\\Schema\\SchemaException' => __DIR__ . '/../../..' . '/lib/public/DB/Schema/SchemaException.php',
413418
'OCP\\DB\\Types' => __DIR__ . '/../../..' . '/lib/public/DB/Types.php',
414419
'OCP\\Dashboard\\IAPIWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IAPIWidget.php',
415420
'OCP\\Dashboard\\IAPIWidgetV2' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IAPIWidgetV2.php',
@@ -1815,6 +1820,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
18151820
'OC\\DB\\SQLiteMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteMigrator.php',
18161821
'OC\\DB\\SQLiteSessionInit' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteSessionInit.php',
18171822
'OC\\DB\\SchemaWrapper' => __DIR__ . '/../../..' . '/lib/private/DB/SchemaWrapper.php',
1823+
'OC\\DB\\Schema\\Column' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Column.php',
1824+
'OC\\DB\\Schema\\Index' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Index.php',
1825+
'OC\\DB\\Schema\\Table' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Table.php',
1826+
'OC\\DB\\Schema\\Type' => __DIR__ . '/../../..' . '/lib/private/DB/Schema/Type.php',
18181827
'OC\\DB\\SetTransactionIsolationLevel' => __DIR__ . '/../../..' . '/lib/private/DB/SetTransactionIsolationLevel.php',
18191828
'OC\\Dashboard\\Manager' => __DIR__ . '/../../..' . '/lib/private/Dashboard/Manager.php',
18201829
'OC\\DatabaseException' => __DIR__ . '/../../..' . '/lib/private/DatabaseException.php',

lib/private/DB/MigrationService.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010

1111
use Doctrine\DBAL\Schema\Index;
1212
use Doctrine\DBAL\Schema\Schema;
13-
use Doctrine\DBAL\Schema\SchemaException;
1413
use Doctrine\DBAL\Schema\Sequence;
1514
use Doctrine\DBAL\Schema\Table;
1615
use Doctrine\DBAL\Types\Type;
1716
use OC\App\InfoParser;
1817
use OC\Migration\SimpleOutput;
1918
use OCP\App\IAppManager;
2019
use OCP\DB\ISchemaWrapper;
20+
use OCP\DB\Schema\SchemaException;
2121
use OCP\DB\Types;
2222
use OCP\IConfig;
2323
use OCP\IDBConnection;
@@ -130,7 +130,7 @@ private function createMigrationTable(): bool {
130130
$column = $table->getColumn('version');
131131
$schemaMismatch = $column->getLength() !== 255;
132132
}
133-
} catch (SchemaException $e) {
133+
} catch (SchemaException|\Doctrine\DBAL\Schema\SchemaException) {
134134
// One of the columns is missing
135135
$schemaMismatch = true;
136136
}
@@ -147,7 +147,7 @@ private function createMigrationTable(): bool {
147147

148148
// Recreate the schema wrapper after dropping the table.
149149
$schema = new SchemaWrapper($this->connection);
150-
} catch (SchemaException $e) {
150+
} catch (SchemaException|\Doctrine\DBAL\Schema\SchemaException) {
151151
// The table does not exist; it will be created below.
152152
}
153153

@@ -550,7 +550,7 @@ public function ensureNamingConstraints(Schema $sourceSchema, Schema $targetSche
550550
foreach ($targetSchema->getTables() as $table) {
551551
try {
552552
$sourceTable = $sourceSchema->getTable($table->getName());
553-
} catch (SchemaException $e) {
553+
} catch (\Doctrine\DBAL\Schema\SchemaException $e) {
554554
// we only validate new tables
555555
if (\strlen($table->getName()) + $prefixLength > $MAX_NAME_LENGTH) {
556556
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
643643
foreach ($targetSchema->getTables() as $table) {
644644
try {
645645
$sourceTable = $sourceSchema->getTable($table->getName());
646-
} catch (SchemaException $e) {
646+
} catch (\Doctrine\DBAL\Schema\SchemaException|SchemaException) {
647647
$sourceTable = null;
648648
}
649649

lib/private/DB/Schema/Column.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
6+
// SPDX-License-Identifier: AGPL-3.0-or-later
7+
8+
namespace OC\DB\Schema;
9+
10+
use Doctrine\DBAL\Schema\Column as DBALColumn;
11+
use Doctrine\DBAL\Schema\SchemaException as DBALSchemaException;
12+
use Doctrine\DBAL\Types\Type as DBALType;
13+
use OCP\DB\Schema\IColumn;
14+
use OCP\DB\Schema\IType;
15+
use OCP\DB\Schema\SchemaException;
16+
17+
/**
18+
* Object representation of a column, wrapping a Doctrine DBAL Column.
19+
*/
20+
class Column implements IColumn {
21+
public function __construct(
22+
private DBALColumn $column,
23+
) {
24+
}
25+
26+
/**
27+
* Returns the wrapped Doctrine DBAL column.
28+
*/
29+
public function getWrappedColumn(): DBALColumn {
30+
return $this->column;
31+
}
32+
33+
#[\Override]
34+
public function setOptions(array $options): self {
35+
try {
36+
$this->column->setOptions($options);
37+
} catch (DBALSchemaException $e) {
38+
throw new SchemaException($e->getMessage(), $e->getCode(), $e);
39+
}
40+
41+
return $this;
42+
}
43+
44+
#[\Override]
45+
public function setType(string|IType|DBALType $type): self {
46+
if ($type instanceof IType) {
47+
$type = $type->getName();
48+
}
49+
50+
$this->column->setType($type instanceof DBALType ? $type : DBALType::getType($type));
51+
52+
return $this;
53+
}
54+
55+
#[\Override]
56+
public function setLength(?int $length): self {
57+
$this->column->setLength($length);
58+
59+
return $this;
60+
}
61+
62+
#[\Override]
63+
public function setPrecision(int $precision): self {
64+
$this->column->setPrecision($precision);
65+
66+
return $this;
67+
}
68+
69+
#[\Override]
70+
public function setScale(int $scale): self {
71+
$this->column->setScale($scale);
72+
73+
return $this;
74+
}
75+
76+
#[\Override]
77+
public function setUnsigned(bool $unsigned): self {
78+
$this->column->setUnsigned($unsigned);
79+
80+
return $this;
81+
}
82+
83+
#[\Override]
84+
public function setFixed(bool $fixed): self {
85+
$this->column->setFixed($fixed);
86+
87+
return $this;
88+
}
89+
90+
#[\Override]
91+
public function setNotnull(bool $notnull): self {
92+
$this->column->setNotnull($notnull);
93+
94+
return $this;
95+
}
96+
97+
#[\Override]
98+
public function setDefault(mixed $default): self {
99+
$this->column->setDefault($default);
100+
101+
return $this;
102+
}
103+
104+
#[\Override]
105+
public function getType(): IType {
106+
return new Type($this->column->getType());
107+
}
108+
109+
#[\Override]
110+
public function getLength(): ?int {
111+
return $this->column->getLength();
112+
}
113+
114+
#[\Override]
115+
public function getPrecision(): int {
116+
return $this->column->getPrecision();
117+
}
118+
119+
#[\Override]
120+
public function getScale(): int {
121+
return $this->column->getScale();
122+
}
123+
124+
#[\Override]
125+
public function getUnsigned(): bool {
126+
return $this->column->getUnsigned();
127+
}
128+
129+
#[\Override]
130+
public function getFixed(): bool {
131+
return $this->column->getFixed();
132+
}
133+
134+
#[\Override]
135+
public function getNotnull(): bool {
136+
return $this->column->getNotnull();
137+
}
138+
139+
#[\Override]
140+
public function getDefault(): mixed {
141+
return $this->column->getDefault();
142+
}
143+
144+
#[\Override]
145+
public function getAutoincrement(): bool {
146+
return $this->column->getAutoincrement();
147+
}
148+
149+
/**
150+
* Forwards any method not declared on IColumn to the wrapped Doctrine
151+
* DBAL column, e.g. read-only accessors like `getName()` or
152+
* `getAutoincrement()` that are not part of the public API.
153+
*/
154+
public function __call(string $name, array $arguments): mixed {
155+
try {
156+
return $this->column->$name(...$arguments);
157+
} catch (DBALSchemaException $e) {
158+
throw new SchemaException($e->getMessage(), $e->getCode(), $e);
159+
}
160+
}
161+
}

lib/private/DB/Schema/Index.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
6+
// SPDX-License-Identifier: AGPL-3.0-or-later
7+
8+
namespace OC\DB\Schema;
9+
10+
use Doctrine\DBAL\Schema\Index as DBALIndex;
11+
use OCP\DB\Schema\IIndex;
12+
13+
/**
14+
* Object representation of an index, wrapping a Doctrine DBAL Index.
15+
*/
16+
class Index implements IIndex {
17+
public function __construct(
18+
private DBALIndex $index,
19+
) {
20+
}
21+
22+
/**
23+
* Returns the wrapped Doctrine DBAL index.
24+
*/
25+
public function getWrappedIndex(): DBALIndex {
26+
return $this->index;
27+
}
28+
29+
#[\Override]
30+
public function getName(): string {
31+
return $this->index->getName();
32+
}
33+
34+
#[\Override]
35+
public function getColumns(): array {
36+
return array_values($this->index->getColumns());
37+
}
38+
39+
#[\Override]
40+
public function isUnique(): bool {
41+
return $this->index->isUnique();
42+
}
43+
44+
#[\Override]
45+
public function isPrimary(): bool {
46+
return $this->index->isPrimary();
47+
}
48+
49+
#[\Override]
50+
public function isSimpleIndex(): bool {
51+
return $this->index->isSimpleIndex();
52+
}
53+
54+
#[\Override]
55+
public function spansColumns(array $columnNames): bool {
56+
return $this->index->spansColumns($columnNames);
57+
}
58+
59+
#[\Override]
60+
public function hasColumnAtPosition(string $name, int $pos = 0): bool {
61+
return $this->index->hasColumnAtPosition($name, $pos);
62+
}
63+
64+
#[\Override]
65+
public function getFlags(): array {
66+
return array_values($this->index->getFlags());
67+
}
68+
69+
#[\Override]
70+
public function hasFlag(string $flag): bool {
71+
return $this->index->hasFlag($flag);
72+
}
73+
74+
#[\Override]
75+
public function hasOption(string $name): bool {
76+
return $this->index->hasOption($name);
77+
}
78+
79+
#[\Override]
80+
public function getOption(string $name): mixed {
81+
return $this->index->getOption($name);
82+
}
83+
84+
#[\Override]
85+
public function getOptions(): array {
86+
return $this->index->getOptions();
87+
}
88+
89+
/**
90+
* Forwards any method not declared on IIndex to the wrapped Doctrine
91+
* DBAL index, e.g. mutators like `addFlag()` or `removeFlag()` that are
92+
* not part of the public API.
93+
*/
94+
public function __call(string $name, array $arguments): mixed {
95+
return $this->index->$name(...$arguments);
96+
}
97+
}

0 commit comments

Comments
 (0)