Skip to content

Commit 9aeddb6

Browse files
committed
enh: UUIDs for Views
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
1 parent be7cff8 commit 9aeddb6

3 files changed

Lines changed: 104 additions & 15 deletions

File tree

lib/Db/View.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OCA\Tables\Model\SortRuleSet;
1616
use OCA\Tables\ResponseDefinitions;
1717
use OCA\Tables\Service\ValueObject\ViewColumnInformation;
18+
use OCA\Tables\Vendor\Symfony\Component\Uid\Uuid;
1819

1920
/**
2021
* @psalm-suppress PropertyNotSetInConstructor
@@ -23,6 +24,8 @@
2324
*
2425
* @method getId(): int
2526
* @method setId(int $id)
27+
* @method string getUuid()
28+
* @method setUuid(?string $uuid)
2629
* @method getTitle(): string
2730
* @method setTitle(string $title)
2831
* @method getTableId(): int
@@ -63,6 +66,7 @@
6366
* @method setOwnership(string $ownership)
6467
*/
6568
class View extends EntitySuper implements JsonSerializable {
69+
protected ?string $uuid = null;
6670
protected ?string $title = null;
6771
protected ?int $tableId = null;
6872
protected ?string $createdBy = null;
@@ -88,9 +92,34 @@ class View extends EntitySuper implements JsonSerializable {
8892

8993
public function __construct() {
9094
$this->addType('id', 'integer');
95+
$this->addType('uuid', 'string');
9196
$this->addType('tableId', 'integer');
9297
}
9398

99+
public function setter(string $name, array $args): void {
100+
if ($name === 'uuid') {
101+
$this->setOrAssignUuid($args[0]);
102+
return;
103+
}
104+
parent::setter($name, $args);
105+
}
106+
107+
private function setOrAssignUuid(?string $uuid): void {
108+
if ($this->uuid !== null) {
109+
throw new \RuntimeException('This view already has a UUID, they are immutable');
110+
}
111+
if ($uuid === null) {
112+
$this->applyUuid(Uuid::v7()->toRfc4122());
113+
return;
114+
}
115+
$this->applyUuid($uuid);
116+
}
117+
118+
private function applyUuid(string $uuid): void {
119+
$this->uuid = $uuid;
120+
$this->markFieldUpdated('uuid');
121+
}
122+
94123
/**
95124
* @psalm-suppress MismatchingDocblockReturnType
96125
* @return int[]
@@ -181,6 +210,7 @@ private function getSharePermissions(): ?Permissions {
181210
public function jsonSerialize(): array {
182211
$serialisedJson = [
183212
'id' => $this->id,
213+
'uuid' => $this->uuid,
184214
'tableId' => ($this->tableId || $this->tableId === 0) ? $this->tableId : -1,
185215
'title' => $this->title ?: '',
186216
'description' => $this->description,

lib/Migration/Version2020Date20260513185340.php

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
use Override;
2121

2222
class Version2020Date20260513185340 extends SimpleMigrationStep {
23-
private const TARGET_TABLE = 'tables_columns';
23+
private const TARGET_TABLE_COLUMNS = 'tables_columns';
24+
private const TARGET_TABLE_VIEWS = 'tables_views';
2425
private const COL_ID = 'id';
2526
private const COL_UUID = 'uuid';
2627
private const COL_SELECTION_OPTIONS = 'selection_options';
27-
private const INDEX_NAME = 'tables_col_uuid_uniq';
28+
private const INDEX_NAME_COLUMNS = 'tables_col_uuid_uniq';
29+
private const INDEX_NAME_VIEWS = 'tables_views_uuid_uniq';
2830

2931
public function __construct(
3032
private readonly IDBConnection $db,
@@ -36,24 +38,28 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
3638
/** @var ISchemaWrapper $schema */
3739
$schema = $schemaClosure();
3840

39-
if (!$schema->hasTable(self::TARGET_TABLE)) {
40-
return null;
41-
}
41+
$this->addUuidColumnToTable($schema, self::TARGET_TABLE_COLUMNS, self::INDEX_NAME_COLUMNS);
42+
$this->addUuidColumnToTable($schema, self::TARGET_TABLE_VIEWS, self::INDEX_NAME_VIEWS);
43+
44+
return $schema;
45+
}
4246

43-
$columnsTable = $schema->getTable(self::TARGET_TABLE);
44-
if (!$columnsTable->hasColumn(self::COL_UUID)) {
45-
$columnsTable->addColumn(self::COL_UUID, Types::STRING, [
47+
private function addUuidColumnToTable(ISchemaWrapper $schema, string $tableName, string $indexName): void {
48+
if (!$schema->hasTable($tableName)) {
49+
return;
50+
}
51+
$targetTable = $schema->getTable($tableName);
52+
if (!$targetTable->hasColumn(self::COL_UUID)) {
53+
$targetTable->addColumn(self::COL_UUID, Types::STRING, [
4654
'notnull' => false,
4755
'default' => null,
4856
'length' => 36,
4957
'comment' => 'UUIDv7 identifier to support structural updates across instances',
5058
]);
5159
}
52-
if (!$columnsTable->hasUniqueConstraint(self::INDEX_NAME)) {
53-
$columnsTable->addUniqueIndex(['table_id', self::COL_UUID], self::INDEX_NAME);
60+
if (!$targetTable->hasUniqueConstraint($indexName)) {
61+
$targetTable->addUniqueIndex(['table_id', self::COL_UUID], $indexName);
5462
}
55-
56-
return $schema;
5763
}
5864

5965
private function applyColumnOptionsUpdateIfNecessary(IQueryBuilder $query, int $columnId, ?string $rawSelectionOptions): void {
@@ -90,19 +96,24 @@ private function applyColumnOptionsUpdateIfNecessary(IQueryBuilder $query, int $
9096

9197
#[Override]
9298
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
99+
$this->fillColumnUuids();
100+
$this->fillViewUuids();
101+
}
102+
103+
private function fillColumnUuids(): void {
93104
$qbColUuidUpdate = $this->db->getQueryBuilder();
94-
$qbColUuidUpdate->update(self::TARGET_TABLE)
105+
$qbColUuidUpdate->update(self::TARGET_TABLE_COLUMNS)
95106
->set(self::COL_UUID, $qbColUuidUpdate->createParameter('columnUuid'))
96107
->where($qbColUuidUpdate->expr()->eq(self::COL_ID, $qbColUuidUpdate->createParameter('columnLocalId')));
97108

98109
$qbColOptionsUuidUpdate = $this->db->getQueryBuilder();
99-
$qbColOptionsUuidUpdate->update(self::TARGET_TABLE)
110+
$qbColOptionsUuidUpdate->update(self::TARGET_TABLE_COLUMNS)
100111
->set(self::COL_SELECTION_OPTIONS, $qbColOptionsUuidUpdate->createParameter('columnSelectionOptions'))
101112
->where($qbColOptionsUuidUpdate->expr()->eq(self::COL_ID, $qbColOptionsUuidUpdate->createParameter('columnLocalId')));
102113

103114
$qbSelect = $this->db->getQueryBuilder();
104115
$qbSelect->select(self::COL_ID, self::COL_SELECTION_OPTIONS)
105-
->from(self::TARGET_TABLE);
116+
->from(self::TARGET_TABLE_COLUMNS);
106117
$select = $qbSelect->executeQuery();
107118

108119
$writeBatches = 250;
@@ -140,4 +151,49 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array
140151

141152
$select->closeCursor();
142153
}
154+
155+
private function fillViewUuids(): void {
156+
$qbColUuidUpdate = $this->db->getQueryBuilder();
157+
$qbColUuidUpdate->update(self::TARGET_TABLE_VIEWS)
158+
->set(self::COL_UUID, $qbColUuidUpdate->createParameter('columnUuid'))
159+
->where($qbColUuidUpdate->expr()->eq(self::COL_ID, $qbColUuidUpdate->createParameter('columnLocalId')));
160+
161+
$qbSelect = $this->db->getQueryBuilder();
162+
$qbSelect->select(self::COL_ID)
163+
->from(self::TARGET_TABLE_VIEWS);
164+
$select = $qbSelect->executeQuery();
165+
166+
$writeBatches = 250;
167+
$updates = 0;
168+
169+
try {
170+
$this->db->beginTransaction();
171+
while (($columnData = $select->fetchAssociative()) !== false) {
172+
$columnId = $columnData[self::COL_ID];
173+
$qbColUuidUpdate->setParameters(
174+
[
175+
'columnLocalId' => (int)$columnId,
176+
'columnUuid' => Uuid::v7()->toRfc4122(),
177+
],
178+
[
179+
Types::INTEGER,
180+
Types::STRING,
181+
]
182+
);
183+
$qbColUuidUpdate->executeStatement();
184+
185+
$updates++;
186+
if ($updates % $writeBatches === 0) {
187+
$this->db->commit();
188+
$this->db->beginTransaction();
189+
}
190+
}
191+
$this->db->commit();
192+
} catch (\Exception $e) {
193+
$this->db->rollBack();
194+
throw $e;
195+
}
196+
197+
$select->closeCursor();
198+
}
143199
}

lib/Service/ViewService.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use OCA\Tables\Model\ViewUpdateInput;
3434
use OCA\Tables\ResponseDefinitions;
3535
use OCA\Tables\Service\ValueObject\ViewColumnInformation;
36+
use OCA\Tables\Vendor\Symfony\Component\Uid\Uuid;
3637
use OCP\AppFramework\Db\DoesNotExistException;
3738
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
3839
use OCP\EventDispatcher\IEventDispatcher;
@@ -209,6 +210,7 @@ public function create(string $title, ?string $emoji, Table $table, ?string $use
209210

210211
$time = new DateTime();
211212
$item = new View();
213+
$item->setUuid(null);
212214
$item->setTitle($title);
213215
if ($emoji) {
214216
$item->setEmoji($emoji);
@@ -634,6 +636,7 @@ public function addColumnToView(View $view, Column $column, ?string $userId = nu
634636
*/
635637
public function importView(int $tableId, array $view, string $userId): void {
636638
$item = new View();
639+
$item->setUuid((isset($view['uuid']) && Uuid::isValid($view['uuid'])) ? $view['uuid'] : null);
637640
$item->setTableId($tableId);
638641
$item->setTitle($view['title']);
639642
$item->setEmoji($view['emoji']);

0 commit comments

Comments
 (0)