Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 17 additions & 27 deletions src/modules/folder/folder.repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@ describe('SequelizeFolderRepository', () => {
const mockResult = {
file_count: '0',
total_size: '0',
total_files_found: '0',
max_depth: null,
};

jest
Expand All @@ -1171,7 +1171,7 @@ describe('SequelizeFolderRepository', () => {
const mockResult = {
file_count: '500',
total_size: '5000000',
total_files_found: '500',
max_depth: '5',
};

jest
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -1241,7 +1241,7 @@ describe('SequelizeFolderRepository', () => {
const mockResult = {
file_count: '9973',
total_size: '27634171904',
total_files_found: '9973',
max_depth: '10',
};

jest
Expand All @@ -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
Expand All @@ -1286,7 +1274,9 @@ describe('SequelizeFolderRepository', () => {
{
replacements: {
folderUuid: folder.uuid,
fileStatusCondition: [FileStatus.EXISTS],
maxDepth: 50,
fileStatus: FileStatus.EXISTS,
maxFiles: 10001,
},
},
);
Expand Down
80 changes: 36 additions & 44 deletions src/modules/folder/folder.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: {
Expand Down
Loading