Skip to content

Commit ab89041

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 30a2c84 commit ab89041

11 files changed

Lines changed: 333 additions & 47 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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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+
/**
21+
* FIXME Auto-generated migration step: Please modify to your needs!
22+
*/
23+
class Version1000Date20260731171922 extends SimpleMigrationStep {
24+
/**
25+
* @param Closure():ISchemaWrapper $schemaClosure
26+
* @throws SchemaException
27+
*/
28+
#[Override]
29+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
30+
$schema = $schemaClosure();
31+
32+
if (!$schema->hasTable('sharing_classmap')) {
33+
$table = $schema->createTable('sharing_classmap');
34+
$table->addColumn('class_id', Types::INTEGER, [
35+
'autoincrement' => true,
36+
'notnull' => true,
37+
]);
38+
$table->addColumn('class_name', Types::STRING, ['length' => 64]);
39+
$table->setPrimaryKey(['class_id']);
40+
$table->addUniqueIndex(['class_name']);
41+
}
42+
43+
$sourcesTable = $schema->getTable('sharing_share_sources');
44+
if ($sourcesTable->hasColumn('source_class')) {
45+
$sourcesTable->dropColumn('source_class');
46+
$sourcesTable->addColumn('source_class_id', Types::INTEGER);
47+
$sourcesTable->dropPrimaryKey();
48+
$sourcesTable->setPrimaryKey(['share_id', 'source_class_id', 'source_value']);
49+
$sourcesTable->addForeignKeyConstraint('sharing_classmap', ['source_class_id'], ['class_id']);
50+
}
51+
52+
$recipientsTable = $schema->getTable('sharing_share_recipients');
53+
if ($recipientsTable->hasColumn('recipient_class')) {
54+
$recipientsTable->dropColumn('recipient_class');
55+
$recipientsTable->addColumn('recipient_class_id', Types::INTEGER);
56+
$recipientsTable->dropPrimaryKey();
57+
$recipientsTable->setPrimaryKey(['share_id', 'recipient_class_id', 'recipient_value']);
58+
$recipientsTable->addForeignKeyConstraint('sharing_classmap', ['recipient_class_id'], ['class_id']);
59+
}
60+
61+
$propertiesTable = $schema->getTable('sharing_share_properties');
62+
if ($propertiesTable->hasColumn('property_class')) {
63+
$propertiesTable->dropColumn('property_class');
64+
$propertiesTable->addColumn('property_class_id', Types::INTEGER);
65+
$propertiesTable->dropPrimaryKey();
66+
$propertiesTable->setPrimaryKey(['share_id', 'property_class_id']);
67+
$propertiesTable->addForeignKeyConstraint('sharing_classmap', ['property_class_id'], ['class_id']);
68+
}
69+
70+
$permissionsTable = $schema->getTable('sharing_share_permissions');
71+
if ($permissionsTable->hasColumn('permission_class')) {
72+
$permissionsTable->dropColumn('permission_class');
73+
$permissionsTable->addColumn('permission_class_id', Types::INTEGER);
74+
$permissionsTable->dropPrimaryKey();
75+
$permissionsTable->setPrimaryKey(['share_id', 'permission_class_id']);
76+
$permissionsTable->addForeignKeyConstraint('sharing_classmap', ['permission_class_id'], ['class_id']);
77+
}
78+
79+
return $schema;
80+
}
81+
82+
#[Override]
83+
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
84+
}
85+
}

‎lib/composer/composer/autoload_classmap.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,6 +2297,7 @@
22972297
'OC\\Share20\\UserDeletedListener' => $baseDir . '/lib/private/Share20/UserDeletedListener.php',
22982298
'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php',
22992299
'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php',
2300+
'OC\\Sharing\\ClassMapper' => $baseDir . '/lib/private/Sharing/ClassMapper.php',
23002301
'OC\\Sharing\\ISharingLegacyBackend' => $baseDir . '/lib/private/Sharing/ISharingLegacyBackend.php',
23012302
'OC\\Sharing\\SharingBackend' => $baseDir . '/lib/private/Sharing/SharingBackend.php',
23022303
'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
@@ -2338,6 +2338,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
23382338
'OC\\Share20\\UserDeletedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserDeletedListener.php',
23392339
'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php',
23402340
'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php',
2341+
'OC\\Sharing\\ClassMapper' => __DIR__ . '/../../..' . '/lib/private/Sharing/ClassMapper.php',
23412342
'OC\\Sharing\\ISharingLegacyBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/ISharingLegacyBackend.php',
23422343
'OC\\Sharing\\SharingBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/SharingBackend.php',
23432344
'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
@@ -1153,6 +1153,7 @@ function () use ($c) {
11531153

11541154
$this->registerAlias(\NCU\Sharing\ISharingRegistry::class, \OC\Sharing\SharingRegistry::class);
11551155
$this->registerAlias(\NCU\Sharing\ISharingManager::class, \OC\Sharing\SharingManager::class);
1156+
$this->registerAlias(\NCU\Sharing\ISharingBackend::class, \OC\Sharing\SharingBackend::class);
11561157

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

0 commit comments

Comments
 (0)