Skip to content

Commit bf1ca51

Browse files
committed
feat: keep a classname mapping instead of always storing the full name
Signed-off-by: Robin Appelman <robin@icewind.nl>
1 parent a2707e1 commit bf1ca51

11 files changed

Lines changed: 335 additions & 48 deletions

File tree

apps/sharing/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@
2626
'OCA\\Sharing\\Controller\\ApiV1Controller' => $baseDir . '/../lib/Controller/ApiV1Controller.php',
2727
'OCA\\Sharing\\Middleware\\ShareApiEnabledMiddleware' => $baseDir . '/../lib/Middleware/ShareApiEnabledMiddleware.php',
2828
'OCA\\Sharing\\Migration\\Version1000Date20250929161325' => $baseDir . '/../lib/Migration/Version1000Date20250929161325.php',
29+
'OCA\\Sharing\\Migration\\Version1000Date20260731171922' => $baseDir . '/../lib/Migration/Version1000Date20260731171922.php',
2930
'OCA\\Sharing\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
3031
);

apps/sharing/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class ComposerStaticInitSharing
4141
'OCA\\Sharing\\Controller\\ApiV1Controller' => __DIR__ . '/..' . '/../lib/Controller/ApiV1Controller.php',
4242
'OCA\\Sharing\\Middleware\\ShareApiEnabledMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/ShareApiEnabledMiddleware.php',
4343
'OCA\\Sharing\\Migration\\Version1000Date20250929161325' => __DIR__ . '/..' . '/../lib/Migration/Version1000Date20250929161325.php',
44+
'OCA\\Sharing\\Migration\\Version1000Date20260731171922' => __DIR__ . '/..' . '/../lib/Migration/Version1000Date20260731171922.php',
4445
'OCA\\Sharing\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
4546
);
4647

apps/sharing/lib/Migration/Version1000Date20250929161325.php

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,17 @@ final class Version1000Date20250929161325 extends SimpleMigrationStep {
2626
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
2727
$schema = $schemaClosure();
2828

29-
// TODO: Add mapping table for class names
3029
// TODO: Check indexes
3130

31+
$mappingTable = $schema->createTable('sharing_classmap');
32+
$mappingTable->addColumn('class_id', Types::INTEGER, [
33+
'autoincrement' => true,
34+
'notnull' => true,
35+
]);
36+
$mappingTable->addColumn('class_name', Types::STRING, ['length' => 64]);
37+
$mappingTable->setPrimaryKey(['class_id']);
38+
$mappingTable->addUniqueIndex(['class_name']);
39+
3240
$shareTable = $schema->createTable('sharing_share');
3341
$shareTable->addColumn('id', Types::BIGINT);
3442
$shareTable->addColumn('owner_user_id', Types::STRING, ['length' => 64]);
@@ -39,38 +47,42 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
3947

4048
$sourcesTable = $schema->createTable('sharing_share_sources');
4149
$sourcesTable->addColumn('share_id', Types::BIGINT);
42-
$sourcesTable->addColumn('source_class', Types::STRING, ['length' => 64]);
50+
$sourcesTable->addColumn('source_class_id', Types::INTEGER);
4351
$sourcesTable->addColumn('source_value', Types::STRING, ['length' => 255]);
44-
$sourcesTable->setPrimaryKey(['share_id', 'source_class', 'source_value']);
52+
$sourcesTable->setPrimaryKey(['share_id', 'source_class_id', 'source_value']);
4553
$sourcesTable->addForeignKeyConstraint($shareTable->getName(), ['share_id'], ['id'], ['onDelete' => 'CASCADE']);
54+
$sourcesTable->addForeignKeyConstraint($mappingTable->getName(), ['source_class_id'], ['class_id']);
4655

4756
// TODO: Add possibility to mask permissions for recipients. For reshares the user may only mask permissions for their child recipients, not their self recipients
4857
$recipientsTable = $schema->createTable('sharing_share_recipients');
4958
$recipientsTable->addColumn('share_id', Types::BIGINT);
50-
$recipientsTable->addColumn('recipient_class', Types::STRING, ['length' => 64]);
59+
$recipientsTable->addColumn('recipient_class_id', Types::INTEGER);
5160
$recipientsTable->addColumn('recipient_value', Types::STRING, ['length' => 255]);
5261
$recipientsTable->addColumn('recipient_instance', Types::STRING, ['length' => 128, 'notnull' => false]);
5362
$recipientsTable->addColumn('recipient_secret', Types::STRING, ['length' => 32]);
5463
$recipientsTable->addColumn('initiator_user_id', Types::STRING, ['length' => 64]);
5564
$recipientsTable->addColumn('initiator_instance', Types::STRING, ['length' => 128, 'notnull' => false]);
56-
$recipientsTable->setPrimaryKey(['share_id', 'recipient_class', 'recipient_value']);
65+
$recipientsTable->setPrimaryKey(['share_id', 'recipient_class_id', 'recipient_value']);
5766
$recipientsTable->addForeignKeyConstraint($shareTable->getName(), ['share_id'], ['id'], ['onDelete' => 'CASCADE']);
5867
// TODO: Maybe needs composite index with share_id
5968
$recipientsTable->addUniqueIndex(['recipient_secret']);
69+
$recipientsTable->addForeignKeyConstraint($mappingTable->getName(), ['recipient_class_id'], ['class_id']);
6070

6171
$propertiesTable = $schema->createTable('sharing_share_properties');
6272
$propertiesTable->addColumn('share_id', Types::BIGINT);
63-
$propertiesTable->addColumn('property_class', Types::STRING, ['length' => 64]);
73+
$propertiesTable->addColumn('property_class_id', Types::INTEGER);
6474
$propertiesTable->addColumn('property_value', Types::STRING, ['length' => 1000, 'notnull' => false]);
65-
$propertiesTable->setPrimaryKey(['share_id', 'property_class']);
75+
$propertiesTable->setPrimaryKey(['share_id', 'property_class_id']);
6676
$propertiesTable->addForeignKeyConstraint($shareTable->getName(), ['share_id'], ['id'], ['onDelete' => 'CASCADE']);
77+
$propertiesTable->addForeignKeyConstraint($mappingTable->getName(), ['property_class_id'], ['class_id']);
6778

6879
$permissionsTable = $schema->createTable('sharing_share_permissions');
6980
$permissionsTable->addColumn('share_id', Types::BIGINT);
70-
$permissionsTable->addColumn('permission_class', Types::STRING, ['length' => 64]);
81+
$permissionsTable->addColumn('permission_class_id', Types::INTEGER);
7182
$permissionsTable->addColumn('permission_enabled', Types::BOOLEAN);
72-
$permissionsTable->setPrimaryKey(['share_id', 'permission_class']);
83+
$permissionsTable->setPrimaryKey(['share_id', 'permission_class_id']);
7384
$permissionsTable->addForeignKeyConstraint($shareTable->getName(), ['share_id'], ['id'], ['onDelete' => 'CASCADE']);
85+
$permissionsTable->addForeignKeyConstraint($mappingTable->getName(), ['permission_class_id'], ['class_id']);
7486

7587
return $schema;
7688
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Sharing\Migration;
11+
12+
use Closure;
13+
use Doctrine\DBAL\Schema\SchemaException;
14+
use OCP\DB\ISchemaWrapper;
15+
use OCP\DB\Types;
16+
use OCP\Migration\IOutput;
17+
use OCP\Migration\SimpleMigrationStep;
18+
use Override;
19+
20+
final class Version1000Date20260731171922 extends SimpleMigrationStep {
21+
/**
22+
* @param Closure():ISchemaWrapper $schemaClosure
23+
* @throws SchemaException
24+
*/
25+
#[Override]
26+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
27+
$schema = $schemaClosure();
28+
29+
if (!$schema->hasTable('sharing_classmap')) {
30+
$table = $schema->createTable('sharing_classmap');
31+
$table->addColumn('class_id', Types::INTEGER, [
32+
'autoincrement' => true,
33+
'notnull' => true,
34+
]);
35+
$table->addColumn('class_name', Types::STRING, ['length' => 64]);
36+
$table->setPrimaryKey(['class_id']);
37+
$table->addUniqueIndex(['class_name']);
38+
}
39+
40+
$sourcesTable = $schema->getTable('sharing_share_sources');
41+
if ($sourcesTable->hasColumn('source_class')) {
42+
$sourcesTable->dropColumn('source_class');
43+
$sourcesTable->addColumn('source_class_id', Types::INTEGER);
44+
$sourcesTable->dropPrimaryKey();
45+
$sourcesTable->setPrimaryKey(['share_id', 'source_class_id', 'source_value']);
46+
$sourcesTable->addForeignKeyConstraint('sharing_classmap', ['source_class_id'], ['class_id']);
47+
}
48+
49+
$recipientsTable = $schema->getTable('sharing_share_recipients');
50+
if ($recipientsTable->hasColumn('recipient_class')) {
51+
$recipientsTable->dropColumn('recipient_class');
52+
$recipientsTable->addColumn('recipient_class_id', Types::INTEGER);
53+
$recipientsTable->dropPrimaryKey();
54+
$recipientsTable->setPrimaryKey(['share_id', 'recipient_class_id', 'recipient_value']);
55+
$recipientsTable->addForeignKeyConstraint('sharing_classmap', ['recipient_class_id'], ['class_id']);
56+
}
57+
58+
$propertiesTable = $schema->getTable('sharing_share_properties');
59+
if ($propertiesTable->hasColumn('property_class')) {
60+
$propertiesTable->dropColumn('property_class');
61+
$propertiesTable->addColumn('property_class_id', Types::INTEGER);
62+
$propertiesTable->dropPrimaryKey();
63+
$propertiesTable->setPrimaryKey(['share_id', 'property_class_id']);
64+
$propertiesTable->addForeignKeyConstraint('sharing_classmap', ['property_class_id'], ['class_id']);
65+
}
66+
67+
$permissionsTable = $schema->getTable('sharing_share_permissions');
68+
if ($permissionsTable->hasColumn('permission_class')) {
69+
$permissionsTable->dropColumn('permission_class');
70+
$permissionsTable->addColumn('permission_class_id', Types::INTEGER);
71+
$permissionsTable->dropPrimaryKey();
72+
$permissionsTable->setPrimaryKey(['share_id', 'permission_class_id']);
73+
$permissionsTable->addForeignKeyConstraint('sharing_classmap', ['permission_class_id'], ['class_id']);
74+
}
75+
76+
return $schema;
77+
}
78+
79+
#[Override]
80+
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
81+
}
82+
}

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,6 +2303,7 @@
23032303
'OC\\Share20\\UserDeletedListener' => $baseDir . '/lib/private/Share20/UserDeletedListener.php',
23042304
'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php',
23052305
'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php',
2306+
'OC\\Sharing\\ClassMapper' => $baseDir . '/lib/private/Sharing/ClassMapper.php',
23062307
'OC\\Sharing\\ISharingLegacyBackend' => $baseDir . '/lib/private/Sharing/ISharingLegacyBackend.php',
23072308
'OC\\Sharing\\SharingBackend' => $baseDir . '/lib/private/Sharing/SharingBackend.php',
23082309
'OC\\Sharing\\SharingManager' => $baseDir . '/lib/private/Sharing/SharingManager.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,6 +2344,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
23442344
'OC\\Share20\\UserDeletedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserDeletedListener.php',
23452345
'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php',
23462346
'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php',
2347+
'OC\\Sharing\\ClassMapper' => __DIR__ . '/../../..' . '/lib/private/Sharing/ClassMapper.php',
23472348
'OC\\Sharing\\ISharingLegacyBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/ISharingLegacyBackend.php',
23482349
'OC\\Sharing\\SharingBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/SharingBackend.php',
23492350
'OC\\Sharing\\SharingManager' => __DIR__ . '/../../..' . '/lib/private/Sharing/SharingManager.php',

lib/private/Server.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,7 @@ function () use ($c) {
11551155

11561156
$this->registerAlias(\NCU\Sharing\ISharingRegistry::class, SharingRegistry::class);
11571157
$this->registerAlias(\NCU\Sharing\ISharingManager::class, SharingManager::class);
1158+
$this->registerAlias(\NCU\Sharing\ISharingBackend::class, \OC\Sharing\SharingBackend::class);
11581159

11591160
$this->connectDispatcher();
11601161
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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+
9+
namespace OC\Sharing;
10+
11+
use OCP\DB\Exception;
12+
use OCP\DB\QueryBuilder\IQueryBuilder;
13+
use OCP\IDBConnection;
14+
15+
class ClassMapper {
16+
/** @var array<int, class-string> $map */
17+
private array $map = [];
18+
19+
private array $reverseMap = [];
20+
21+
private bool $loaded = false;
22+
23+
public function __construct(
24+
private readonly IDBConnection $connection,
25+
) {
26+
}
27+
28+
/**
29+
* @param array{class_id: int|string, class_name: string} $row
30+
*/
31+
private function insertRow(array $row): void {
32+
$id = (int)$row['class_id'];
33+
$class = $row['class_name'];
34+
$this->map[$id] = $class;
35+
$this->reverseMap[$class] = $id;
36+
}
37+
38+
private function loadFromDb(): void {
39+
if ($this->loaded) {
40+
return;
41+
}
42+
43+
$query = $this->connection->getTypedQueryBuilder();
44+
$query->selectColumns('class_id', 'class_name')
45+
->from('sharing_classmap');
46+
$rows = $query->executeQuery()->fetchAll();
47+
48+
foreach ($rows as $row) {
49+
/** @var array{class_id: int|string, class_name: string} $row */
50+
$this->insertRow($row);
51+
}
52+
53+
$this->loaded = true;
54+
}
55+
56+
private function loadFromDbByName(string $className): ?int {
57+
$query = $this->connection->getTypedQueryBuilder();
58+
$query->selectColumns('class_id', 'class_name')
59+
->from('sharing_classmap')
60+
->where($query->expr()->eq('class_name', $query->createNamedParameter($className)));
61+
$row = $query->executeQuery()->fetchAssociative();
62+
63+
if ($row) {
64+
/** @var array{class_id: int|string, class_name: string} $row */
65+
$this->insertRow($row);
66+
return (int)$row['class_id'];
67+
}
68+
69+
return null;
70+
}
71+
72+
private function loadFromDbById(int $id): ?string {
73+
$query = $this->connection->getTypedQueryBuilder();
74+
$query->selectColumns('class_id', 'class_name')
75+
->from('sharing_classmap')
76+
->where($query->expr()->eq('class_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
77+
$row = $query->executeQuery()->fetchAssociative();
78+
79+
if ($row) {
80+
/** @var array{class_id: int|string, class_name: string} $row */
81+
$this->insertRow($row);
82+
return $row['class_name'];
83+
}
84+
85+
return null;
86+
}
87+
88+
private function insert(string $className): int {
89+
$query = $this->connection->getTypedQueryBuilder();
90+
$query->insert('sharing_classmap')
91+
->values([
92+
'class_name' => $query->createNamedParameter($className)
93+
]);
94+
try {
95+
$query->executeStatement();
96+
$id = $query->getLastInsertId();
97+
$this->map[$id] = $className;
98+
$this->reverseMap[$className] = $id;
99+
return $id;
100+
} catch (Exception $exception) {
101+
// handle concurrent inserts
102+
if ($exception->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
103+
$id = $this->loadFromDbByName($className);
104+
if (!$id) {
105+
throw new \Exception(sprintf("Failed to insert '%s' into sharing_classmap, duplicate on insert but can't fetch it either", $className), $exception->getCode(), $exception);
106+
}
107+
108+
return $id;
109+
}
110+
111+
throw $exception;
112+
}
113+
}
114+
115+
public function getClassId(string $class): int {
116+
$this->loadFromDb();
117+
118+
return $this->reverseMap[$class] ?? $this->insert($class);
119+
}
120+
121+
public function getClassName(int $id): string {
122+
$this->loadFromDb();
123+
if (isset($this->map[$id])) {
124+
return $this->map[$id];
125+
}
126+
127+
$class = $this->loadFromDbById($id);
128+
if ($class) {
129+
return $class;
130+
}
131+
132+
throw new \Exception(sprintf("Unknown mapped class '%d'", $id));
133+
}
134+
135+
}

0 commit comments

Comments
 (0)