diff --git a/src/modules/folder/folder.repository.spec.ts b/src/modules/folder/folder.repository.spec.ts index a555ef67c..e1a6e4779 100644 --- a/src/modules/folder/folder.repository.spec.ts +++ b/src/modules/folder/folder.repository.spec.ts @@ -1152,7 +1152,7 @@ describe('SequelizeFolderRepository', () => { const mockResult = { file_count: '0', total_size: '0', - total_files_found: '0', + max_depth: null, }; jest @@ -1171,7 +1171,7 @@ describe('SequelizeFolderRepository', () => { const mockResult = { file_count: '500', total_size: '5000000', - total_files_found: '500', + max_depth: '5', }; jest @@ -1188,9 +1188,9 @@ describe('SequelizeFolderRepository', () => { it('When folder reaches maximum file count, then it should return exact count at boundary', async () => { const mockResult = { - file_count: '1000', + file_count: '10000', total_size: '10000000', - total_files_found: '1000', + max_depth: '5', }; jest @@ -1199,15 +1199,15 @@ describe('SequelizeFolderRepository', () => { const result = await repository.calculateFolderStats(folder.uuid); - expect(result.fileCount).toBe(1000); + expect(result.fileCount).toBe(10000); expect(result.isFileCountExact).toBe(true); }); it('When folder exceeds maximum file count, then it should cap count and mark as approximate', async () => { const mockResult = { - file_count: '1500', + file_count: '10001', total_size: '15000000', - total_files_found: '1500', + max_depth: '5', }; jest @@ -1216,15 +1216,15 @@ describe('SequelizeFolderRepository', () => { const result = await repository.calculateFolderStats(folder.uuid); - expect(result.fileCount).toBe(1000); + expect(result.fileCount).toBe(10000); expect(result.isFileCountExact).toBe(false); }); it('When folder exceeds maximum total items, then it should mark size as approximate', async () => { const mockResult = { - file_count: '12000', + file_count: '10001', total_size: '50000000', - total_files_found: '12000', + max_depth: '5', }; jest @@ -1241,7 +1241,7 @@ describe('SequelizeFolderRepository', () => { const mockResult = { file_count: '9973', total_size: '27634171904', - total_files_found: '9973', + max_depth: '10', }; jest @@ -1250,29 +1250,17 @@ describe('SequelizeFolderRepository', () => { const result = await repository.calculateFolderStats(folder.uuid); - expect(result.fileCount).toBe(1000); + expect(result.fileCount).toBe(9973); expect(result.totalSize).toBe(27634171904); - expect(result.isFileCountExact).toBe(false); + expect(result.isFileCountExact).toBe(true); expect(result.isTotalSizeExact).toBe(true); }); - it('When stats calculation times out, then it should throw timeout exception', async () => { - jest.spyOn(FolderModel.sequelize, 'query').mockRejectedValueOnce({ - original: { - code: TIMEOUT_ERROR_CODE, - }, - }); - - await expect( - repository.calculateFolderStats(folder.uuid), - ).rejects.toThrow(CalculateFolderSizeTimeoutException); - }); - it('When folder stats are requested, then only existent files are counted', async () => { const mockResult = { file_count: '100', total_size: '1000000', - total_files_found: '100', + max_depth: '5', }; jest @@ -1286,7 +1274,9 @@ describe('SequelizeFolderRepository', () => { { replacements: { folderUuid: folder.uuid, - fileStatusCondition: [FileStatus.EXISTS], + maxDepth: 50, + fileStatus: FileStatus.EXISTS, + maxFiles: 10001, }, }, ); diff --git a/src/modules/folder/folder.repository.ts b/src/modules/folder/folder.repository.ts index e45a36324..24cf8dd7e 100644 --- a/src/modules/folder/folder.repository.ts +++ b/src/modules/folder/folder.repository.ts @@ -939,16 +939,16 @@ export class SequelizeFolderRepository implements FolderRepository { totalSize: number; isTotalSizeExact: boolean; }> { - try { - const fileStatusCondition = [FileStatus.EXISTS]; + const MAX_FILES = 1001; + const MAX_DEPTH = 50; - const calculateStatsQuery = ` + const calculateStatsQuery = ` WITH RECURSIVE folder_recursive AS ( SELECT fl1.uuid, fl1.parent_uuid, - 1 as depth, - fl1.user_id as owner_id + 1 AS depth, + fl1.user_id AS owner_id FROM folders fl1 WHERE fl1.uuid = :folderUuid AND fl1.removed = FALSE @@ -962,57 +962,49 @@ export class SequelizeFolderRepository implements FolderRepository { fr.depth + 1, fr.owner_id FROM folders fl2 - INNER JOIN folder_recursive fr - ON fr.uuid = fl2.parent_uuid - WHERE fr.depth < 100000 + INNER JOIN folder_recursive fr ON fr.uuid = fl2.parent_uuid + WHERE fr.depth < :maxDepth AND fl2.user_id = fr.owner_id AND fl2.removed = FALSE AND fl2.deleted = FALSE ), - ranked_files AS ( - SELECT - f.uuid, - f.size, - ROW_NUMBER() OVER (ORDER BY f.creation_time) as rn + limited_files AS ( + SELECT f.uuid, f.size, fr.depth FROM folder_recursive fr - INNER JOIN files f - ON f.folder_uuid = fr.uuid - AND f.status IN (:fileStatusCondition) + INNER JOIN files f ON f.folder_uuid = fr.uuid + WHERE f.status = :fileStatus + LIMIT :maxFiles ) SELECT - COUNT(uuid) as file_count, - COALESCE(SUM(size), 0) as total_size, - MAX(rn) as total_files_found - FROM ranked_files - WHERE rn <= 10000; + COUNT(*) AS file_count, + COALESCE(SUM(size), 0) AS total_size, + MAX(depth) AS max_depth + FROM limited_files `; - const [[result]]: any = await FolderModel.sequelize.query( - calculateStatsQuery, - { - replacements: { - folderUuid, - fileStatusCondition, - }, + const [[result]]: any = await FolderModel.sequelize.query( + calculateStatsQuery, + { + replacements: { + folderUuid, + maxDepth: MAX_DEPTH, + fileStatus: FileStatus.EXISTS, + maxFiles: MAX_FILES + 1, }, - ); - - const fileCount = Number.parseInt(result.file_count); - const totalFilesFound = Number.parseInt(result.total_files_found || 0); + }, + ); - return { - fileCount: Math.min(fileCount, 1000), - isFileCountExact: totalFilesFound <= 1000, - totalSize: Number.parseInt(result.total_size), - isTotalSizeExact: totalFilesFound < 10000, - }; - } catch (error) { - if (error.original?.code === '57014') { - throw new CalculateFolderSizeTimeoutException(); - } + const fileCount = Number.parseInt(result.file_count); + const hitFileLimit = fileCount > MAX_FILES; + const hitDepthLimit = Number.parseInt(result.max_depth) >= MAX_DEPTH; + const isExact = !hitFileLimit && !hitDepthLimit; - throw error; - } + return { + fileCount: Math.min(fileCount, MAX_FILES), + totalSize: Number.parseInt(result.total_size), + isFileCountExact: isExact, + isTotalSizeExact: isExact, + }; } async getDeletedFoldersWithNotDeletedChildren(options: {