Skip to content

Commit 9d98f85

Browse files
authored
Merge pull request nextcloud#54793 from nextcloud/fix/fileaccess-getbyancestorinstorage-sharding
fix(FileAccess): Make getByAncestorInStorage sharding ready
2 parents 8e5f436 + 3522a33 commit 9d98f85

File tree

2 files changed

+50
-14
lines changed

2 files changed

+50
-14
lines changed

lib/private/Files/Cache/FileAccess.php

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,20 @@ public function getByAncestorInStorage(int $storageId, int $folderId, int $fileI
114114

115115
$path = $root['path'] === '' ? '' : $root['path'] . '/';
116116

117-
$qb->selectDistinct('*')
117+
$qb->selectDistinct('f.*')
118118
->from('filecache', 'f')
119119
->where($qb->expr()->like('f.path', $qb->createNamedParameter($this->connection->escapeLikeParameter($path) . '%')))
120120
->andWhere($qb->expr()->eq('f.storage', $qb->createNamedParameter($storageId)))
121-
->andWhere($qb->expr()->gt('f.fileid', $qb->createNamedParameter($fileIdCursor, IQueryBuilder::PARAM_INT)));
121+
->andWhere($qb->expr()->gt('f.fileid', $qb->createNamedParameter($fileIdCursor, IQueryBuilder::PARAM_INT)))
122+
->hintShardKey('storage', $storageId);
122123

123-
if (!$endToEndEncrypted) {
124+
if (!$endToEndEncrypted && $this->connection->getShardDefinition('filecache') === null) {
124125
// End to end encrypted files are descendants of a folder with encrypted=1
125-
// Use a subquery to check the `encrypted` status of the parent folder
126-
$subQuery = $this->getQuery()->select('p.encrypted')
127-
->from('filecache', 'p')
128-
->andWhere($qb->expr()->eq('p.fileid', 'f.parent'))
129-
->getSQL();
126+
// We can only do this inner join if the filecache table is not sharded
127+
$qb->innerJoin('f', 'filecache', 'f2', $qb->expr()->eq('f2.fileid', 'f.parent'));
130128

131129
$qb->andWhere(
132-
$qb->expr()->eq($qb->createFunction(sprintf('(%s)', $subQuery)), $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))
130+
$qb->expr()->eq('f2.encrypted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))
133131
);
134132
}
135133

@@ -148,11 +146,42 @@ public function getByAncestorInStorage(int $storageId, int $folderId, int $fileI
148146
$qb->orderBy('f.fileid', 'ASC');
149147
$files = $qb->executeQuery();
150148

151-
while (
152-
/** @var array */
153-
$row = $files->fetch()
154-
) {
155-
yield Cache::cacheEntryFromData($row, $this->mimeTypeLoader);
149+
if (!$endToEndEncrypted && $this->connection->getShardDefinition('filecache') !== null) {
150+
// End to end encrypted files are descendants of a folder with encrypted=1
151+
// If the filecache table is sharded we need to check with a separate query if the parent is encrypted
152+
$rows = [];
153+
do {
154+
while (count($rows) < 1000 && ($row = $files->fetch())) {
155+
$rows[] = $row;
156+
}
157+
$parents = array_map(function ($row) {
158+
return $row['parent'];
159+
}, $rows);
160+
161+
$parentQuery = $this->getQuery();
162+
$parentQuery->select('fileid', 'encrypted')->from('filecache');
163+
$parentQuery->where($parentQuery->expr()->in('fileid', $parentQuery->createNamedParameter($parents, IQueryBuilder::PARAM_INT_ARRAY)));
164+
$parentQuery->hintShardKey('storage', $storageId);
165+
$result = $parentQuery->executeQuery();
166+
$parentRows = $result->fetchAll();
167+
$result->closeCursor();
168+
169+
$encryptedByFileId = array_column($parentRows, 'encrypted', 'fileid');
170+
foreach ($rows as $row) {
171+
if ($encryptedByFileId[$row['parent']]) {
172+
continue;
173+
}
174+
yield Cache::cacheEntryFromData($row, $this->mimeTypeLoader);
175+
}
176+
$rows = [];
177+
} while ($rows[] = $files->fetch());
178+
} else {
179+
while (
180+
/** @var array */
181+
$row = $files->fetch()
182+
) {
183+
yield Cache::cacheEntryFromData($row, $this->mimeTypeLoader);
184+
}
156185
}
157186

158187
$files->closeCursor();

tests/lib/Files/Cache/FileAccessTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ private function setUpTestDatabaseForGetByAncestorInStorage(): void {
211211
'name' => $queryBuilder->createNamedParameter('files'),
212212
'mimetype' => 1,
213213
'encrypted' => 0,
214+
'size' => 1,
214215
])
215216
->executeStatement();
216217

@@ -224,6 +225,7 @@ private function setUpTestDatabaseForGetByAncestorInStorage(): void {
224225
'name' => $queryBuilder->createNamedParameter('documents'),
225226
'mimetype' => 2,
226227
'encrypted' => 1,
228+
'size' => 1,
227229
])
228230
->executeStatement();
229231

@@ -237,6 +239,7 @@ private function setUpTestDatabaseForGetByAncestorInStorage(): void {
237239
'name' => $queryBuilder->createNamedParameter('photos'),
238240
'mimetype' => 3,
239241
'encrypted' => 1,
242+
'size' => 1,
240243
])
241244
->executeStatement();
242245

@@ -250,6 +253,7 @@ private function setUpTestDatabaseForGetByAncestorInStorage(): void {
250253
'name' => $queryBuilder->createNamedParameter('endtoendencrypted'),
251254
'mimetype' => 4,
252255
'encrypted' => 0,
256+
'size' => 1,
253257
])
254258
->executeStatement();
255259

@@ -263,6 +267,7 @@ private function setUpTestDatabaseForGetByAncestorInStorage(): void {
263267
'name' => $queryBuilder->createNamedParameter('serversideencrypted'),
264268
'mimetype' => 4,
265269
'encrypted' => 1,
270+
'size' => 1,
266271
])
267272
->executeStatement();
268273

@@ -276,6 +281,7 @@ private function setUpTestDatabaseForGetByAncestorInStorage(): void {
276281
'name' => $queryBuilder->createNamedParameter('storage2'),
277282
'mimetype' => 5,
278283
'encrypted' => 0,
284+
'size' => 1,
279285
])
280286
->executeStatement();
281287

@@ -289,6 +295,7 @@ private function setUpTestDatabaseForGetByAncestorInStorage(): void {
289295
'name' => $queryBuilder->createNamedParameter('file'),
290296
'mimetype' => 6,
291297
'encrypted' => 0,
298+
'size' => 1,
292299
])
293300
->executeStatement();
294301
}

0 commit comments

Comments
 (0)