Skip to content

Commit e6833d4

Browse files
authored
Merge pull request #62904 from nextcloud/unified-sharing-classmap
feat: keep a classname mapping instead of always storing the full name for shares
2 parents 6361ddd + 4b8139a commit e6833d4

13 files changed

Lines changed: 538 additions & 149 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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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\Attributes\AddColumn;
17+
use OCP\Migration\Attributes\AddIndex;
18+
use OCP\Migration\Attributes\CreateTable;
19+
use OCP\Migration\Attributes\DropColumn;
20+
use OCP\Migration\Attributes\DropIndex;
21+
use OCP\Migration\Attributes\IndexType;
22+
use OCP\Migration\IOutput;
23+
use OCP\Migration\SimpleMigrationStep;
24+
use Override;
25+
26+
#[CreateTable(table: 'sharing_classmap')]
27+
#[DropColumn(table: 'sharing_share_sources', name: 'source_class')]
28+
#[DropColumn(table: 'sharing_share_recipients', name: 'recipient_class')]
29+
#[DropColumn(table: 'sharing_share_properties', name: 'property_class')]
30+
#[DropColumn(table: 'sharing_share_permissions', name: 'permission_class')]
31+
#[AddColumn(table: 'sharing_share_sources', name: 'source_class_id')]
32+
#[AddColumn(table: 'sharing_share_recipients', name: 'recipient_class_id')]
33+
#[AddColumn(table: 'sharing_share_properties', name: 'property_class_id')]
34+
#[AddColumn(table: 'sharing_share_permissions', name: 'permission_class_id')]
35+
#[DropIndex(table: 'sharing_share_sources', type: IndexType::PRIMARY)]
36+
#[DropIndex(table: 'sharing_share_recipients', type: IndexType::PRIMARY)]
37+
#[DropIndex(table: 'sharing_share_properties', type: IndexType::PRIMARY)]
38+
#[DropIndex(table: 'sharing_share_permissions', type: IndexType::PRIMARY)]
39+
#[AddIndex(table: 'sharing_share_sources', type: IndexType::PRIMARY)]
40+
#[AddIndex(table: 'sharing_share_recipients', type: IndexType::PRIMARY)]
41+
#[AddIndex(table: 'sharing_share_properties', type: IndexType::PRIMARY)]
42+
#[AddIndex(table: 'sharing_share_permissions', type: IndexType::PRIMARY)]
43+
final class Version1000Date20260731171922 extends SimpleMigrationStep {
44+
/**
45+
* @param Closure():ISchemaWrapper $schemaClosure
46+
* @throws SchemaException
47+
*/
48+
#[Override]
49+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
50+
$schema = $schemaClosure();
51+
52+
if (!$schema->hasTable('sharing_classmap')) {
53+
$table = $schema->createTable('sharing_classmap');
54+
$table->addColumn('class_id', Types::INTEGER, [
55+
'autoincrement' => true,
56+
'notnull' => true,
57+
]);
58+
$table->addColumn('class_name', Types::STRING, ['length' => 64]);
59+
$table->setPrimaryKey(['class_id']);
60+
$table->addUniqueIndex(['class_name']);
61+
}
62+
63+
$sourcesTable = $schema->getTable('sharing_share_sources');
64+
if ($sourcesTable->hasColumn('source_class')) {
65+
$sourcesTable->dropColumn('source_class');
66+
$sourcesTable->addColumn('source_class_id', Types::INTEGER);
67+
$sourcesTable->dropPrimaryKey();
68+
$sourcesTable->setPrimaryKey(['share_id', 'source_class_id', 'source_value']);
69+
$sourcesTable->addForeignKeyConstraint('sharing_classmap', ['source_class_id'], ['class_id']);
70+
}
71+
72+
$recipientsTable = $schema->getTable('sharing_share_recipients');
73+
if ($recipientsTable->hasColumn('recipient_class')) {
74+
$recipientsTable->dropColumn('recipient_class');
75+
$recipientsTable->addColumn('recipient_class_id', Types::INTEGER);
76+
$recipientsTable->dropPrimaryKey();
77+
$recipientsTable->setPrimaryKey(['share_id', 'recipient_class_id', 'recipient_value']);
78+
$recipientsTable->addForeignKeyConstraint('sharing_classmap', ['recipient_class_id'], ['class_id']);
79+
}
80+
81+
$propertiesTable = $schema->getTable('sharing_share_properties');
82+
if ($propertiesTable->hasColumn('property_class')) {
83+
$propertiesTable->dropColumn('property_class');
84+
$propertiesTable->addColumn('property_class_id', Types::INTEGER);
85+
$propertiesTable->dropPrimaryKey();
86+
$propertiesTable->setPrimaryKey(['share_id', 'property_class_id']);
87+
$propertiesTable->addForeignKeyConstraint('sharing_classmap', ['property_class_id'], ['class_id']);
88+
}
89+
90+
$permissionsTable = $schema->getTable('sharing_share_permissions');
91+
if ($permissionsTable->hasColumn('permission_class')) {
92+
$permissionsTable->dropColumn('permission_class');
93+
$permissionsTable->addColumn('permission_class_id', Types::INTEGER);
94+
$permissionsTable->dropPrimaryKey();
95+
$permissionsTable->setPrimaryKey(['share_id', 'permission_class_id']);
96+
$permissionsTable->addForeignKeyConstraint('sharing_classmap', ['permission_class_id'], ['class_id']);
97+
}
98+
99+
return $schema;
100+
}
101+
102+
#[Override]
103+
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
104+
}
105+
}

lib/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
'NCU\\Security\\Signature\\ISignatureManager' => $baseDir . '/lib/unstable/Security/Signature/ISignatureManager.php',
3838
'NCU\\Security\\Signature\\ISignedRequest' => $baseDir . '/lib/unstable/Security/Signature/ISignedRequest.php',
3939
'NCU\\Security\\Signature\\Model\\Signatory' => $baseDir . '/lib/unstable/Security/Signature/Model/Signatory.php',
40+
'NCU\\Sharing\\Event\\SharesDefaultSetEvent' => $baseDir . '/lib/unstable/Sharing/Event/SharesDefaultSetEvent.php',
4041
'NCU\\Sharing\\Exception\\AShareException' => $baseDir . '/lib/unstable/Sharing/Exception/AShareException.php',
4142
'NCU\\Sharing\\Exception\\ShareInvalidException' => $baseDir . '/lib/unstable/Sharing/Exception/ShareInvalidException.php',
4243
'NCU\\Sharing\\Exception\\ShareNotFoundException' => $baseDir . '/lib/unstable/Sharing/Exception/ShareNotFoundException.php',
@@ -2308,6 +2309,7 @@
23082309
'OC\\Share20\\UserDeletedListener' => $baseDir . '/lib/private/Share20/UserDeletedListener.php',
23092310
'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php',
23102311
'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php',
2312+
'OC\\Sharing\\ClassMapper' => $baseDir . '/lib/private/Sharing/ClassMapper.php',
23112313
'OC\\Sharing\\ISharingLegacyBackend' => $baseDir . '/lib/private/Sharing/ISharingLegacyBackend.php',
23122314
'OC\\Sharing\\SharingBackend' => $baseDir . '/lib/private/Sharing/SharingBackend.php',
23132315
'OC\\Sharing\\SharingManager' => $baseDir . '/lib/private/Sharing/SharingManager.php',

lib/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
7878
'NCU\\Security\\Signature\\ISignatureManager' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/ISignatureManager.php',
7979
'NCU\\Security\\Signature\\ISignedRequest' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/ISignedRequest.php',
8080
'NCU\\Security\\Signature\\Model\\Signatory' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/Signatory.php',
81+
'NCU\\Sharing\\Event\\SharesDefaultSetEvent' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Event/SharesDefaultSetEvent.php',
8182
'NCU\\Sharing\\Exception\\AShareException' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Exception/AShareException.php',
8283
'NCU\\Sharing\\Exception\\ShareInvalidException' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Exception/ShareInvalidException.php',
8384
'NCU\\Sharing\\Exception\\ShareNotFoundException' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Exception/ShareNotFoundException.php',
@@ -2349,6 +2350,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
23492350
'OC\\Share20\\UserDeletedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserDeletedListener.php',
23502351
'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php',
23512352
'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php',
2353+
'OC\\Sharing\\ClassMapper' => __DIR__ . '/../../..' . '/lib/private/Sharing/ClassMapper.php',
23522354
'OC\\Sharing\\ISharingLegacyBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/ISharingLegacyBackend.php',
23532355
'OC\\Sharing\\SharingBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/SharingBackend.php',
23542356
'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
@@ -1156,6 +1156,7 @@ function () use ($c) {
11561156

11571157
$this->registerAlias(\NCU\Sharing\ISharingRegistry::class, SharingRegistry::class);
11581158
$this->registerAlias(\NCU\Sharing\ISharingManager::class, SharingManager::class);
1159+
$this->registerAlias(\NCU\Sharing\ISharingBackend::class, \OC\Sharing\SharingBackend::class);
11591160

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

0 commit comments

Comments
 (0)