Skip to content

Commit b21c419

Browse files
committed
perf(files/cache): SQL aggregation for folder size calculation
Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
1 parent f8dbe23 commit b21c419

4 files changed

Lines changed: 103 additions & 31 deletions

File tree

lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,14 @@ public function least($x, $y): IQueryFunction {
102102
public function now(): IQueryFunction {
103103
return new QueryFunction('NOW()');
104104
}
105+
106+
#[Override]
107+
public function caseWhen($condition, $then, $else): IQueryFunction {
108+
return new QueryFunction(
109+
'CASE WHEN ' . $condition
110+
. ' THEN ' . $this->helper->quoteColumnName($then)
111+
. ' ELSE ' . $this->helper->quoteColumnName($else)
112+
. ' END'
113+
);
114+
}
105115
}

lib/private/Files/Cache/Cache.php

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,6 @@ public function calculateFolderSize($path, $entry = null) {
10151015
/**
10161016
* inner function because we can't add new params to the public function without breaking any child classes
10171017
*
1018-
* @param string $path
10191018
* @param array|null|ICacheEntry $entry (optional) meta data of the folder
10201019
* @param bool $ignoreUnknown don't mark the folder size as unknown if any of it's children are unknown
10211020
* @return int|float
@@ -1029,7 +1028,17 @@ protected function calculateFolderSizeInner(string $path, $entry = null, bool $i
10291028
$id = $entry['fileid'];
10301029

10311030
$query = $this->getQueryBuilder();
1032-
$query->select('size', 'unencrypted_size')
1031+
// $effectiveSize is reused in both aggregates; Doctrine handles the duplicated parameter correctly
1032+
$effectiveSize = $query->func()->caseWhen(
1033+
$query->expr()->gt('unencrypted_size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)),
1034+
'unencrypted_size',
1035+
'size'
1036+
);
1037+
$query->selectAlias($query->func()->sum('size'), 'size_sum')
1038+
->selectAlias($query->func()->min('size'), 'size_min')
1039+
->selectAlias($query->func()->max('unencrypted_size'), 'unencrypted_max')
1040+
->selectAlias($query->func()->sum($effectiveSize), 'unencrypted_sum')
1041+
->selectAlias($query->func()->min($effectiveSize), 'unencrypted_min')
10331042
->from('filecache')
10341043
->whereStorageId($this->getNumericStorageId())
10351044
->whereParent($id);
@@ -1038,34 +1047,19 @@ protected function calculateFolderSizeInner(string $path, $entry = null, bool $i
10381047
}
10391048

10401049
$result = $query->executeQuery();
1041-
$rows = $result->fetchAll();
1050+
$agg = $result->fetch();
10421051
$result->closeCursor();
10431052

1044-
if ($rows) {
1045-
$sizes = array_map(function (array $row) {
1046-
return Util::numericToNumber($row['size']);
1047-
}, $rows);
1048-
$unencryptedOnlySizes = array_map(function (array $row) {
1049-
return Util::numericToNumber($row['unencrypted_size']);
1050-
}, $rows);
1051-
$unencryptedSizes = array_map(function (array $row) {
1052-
return Util::numericToNumber(($row['unencrypted_size'] > 0) ? $row['unencrypted_size'] : $row['size']);
1053-
}, $rows);
1054-
1055-
$sum = array_sum($sizes);
1056-
$min = min($sizes);
1057-
1058-
$unencryptedSum = array_sum($unencryptedSizes);
1059-
$unencryptedMin = min($unencryptedSizes);
1060-
$unencryptedMax = max($unencryptedOnlySizes);
1061-
1062-
$sum = 0 + $sum;
1063-
$min = 0 + $min;
1064-
if ($min === -1) {
1065-
$totalSize = $min;
1066-
} else {
1067-
$totalSize = $sum;
1068-
}
1053+
// SUM() returns NULL on empty set
1054+
if ($agg && $agg['size_sum'] !== null) {
1055+
$sum = Util::numericToNumber($agg['size_sum']);
1056+
$min = Util::numericToNumber($agg['size_min']);
1057+
$unencryptedMax = Util::numericToNumber($agg['unencrypted_max'] ?? 0);
1058+
$unencryptedSum = Util::numericToNumber($agg['unencrypted_sum'] ?? 0);
1059+
$unencryptedMin = Util::numericToNumber($agg['unencrypted_min'] ?? 0);
1060+
1061+
$totalSize = ($min === -1) ? $min : $sum;
1062+
10691063
if ($unencryptedMin === -1 || $min === -1) {
10701064
$unencryptedTotal = $unencryptedMin;
10711065
} else {
@@ -1077,15 +1071,16 @@ protected function calculateFolderSizeInner(string $path, $entry = null, bool $i
10771071
$unencryptedMax = 0;
10781072
}
10791073

1080-
// only set unencrypted size for a folder if any child entries have it set, or the folder is empty
1074+
// only set unencrypted size for a folder if any child entries have it set
1075+
// or if the folder is empty
10811076
$shouldWriteUnEncryptedSize = $unencryptedMax > 0 || $totalSize === 0 || ($entry['unencrypted_size'] ?? 0) > 0;
10821077
if ($entry['size'] !== $totalSize || (($entry['unencrypted_size'] ?? 0) !== $unencryptedTotal && $shouldWriteUnEncryptedSize)) {
10831078
if ($shouldWriteUnEncryptedSize) {
1084-
// if all children have an unencrypted size of 0, just set the folder unencrypted size to 0 instead of summing the sizes
1079+
// if all children have an unencrypted size of 0
1080+
// just set the folder unencrypted size to 0 instead of summing the sizes
10851081
if ($unencryptedMax === 0) {
10861082
$unencryptedTotal = 0;
10871083
}
1088-
10891084
$this->update($id, [
10901085
'size' => $totalSize,
10911086
'unencrypted_size' => $unencryptedTotal,

lib/public/DB/QueryBuilder/IFunctionBuilder.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,15 @@ public function least($x, $y): IQueryFunction;
176176
* @since 34.0.0
177177
*/
178178
public function now(): IQueryFunction;
179+
180+
/**
181+
* Builds a simple CASE WHEN … THEN … ELSE … END expression.
182+
*
183+
* @param string|IQueryFunction $condition SQL condition (e.g. from `$expr->gt(…)`)
184+
* @param string|ILiteral|IParameter|IQueryFunction $then Result when condition is true
185+
* @param string|ILiteral|IParameter|IQueryFunction $else Result when condition is false
186+
* @return IQueryFunction
187+
* @since 34.0.0
188+
*/
189+
public function caseWhen($condition, $then, $else): IQueryFunction;
179190
}

tests/lib/DB/QueryBuilder/FunctionBuilderTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,4 +486,60 @@ public function testLeast(): void {
486486
$result->closeCursor();
487487
$this->assertEquals(1, $row);
488488
}
489+
490+
public function testCaseWhenTrue(): void {
491+
$query = $this->connection->getQueryBuilder();
492+
493+
$caseExpression = $query->func()->caseWhen(
494+
$query->expr()->gt(new Literal(5), new Literal(3)),
495+
new Literal("'yes'"),
496+
new Literal("'no'")
497+
);
498+
$query->select($caseExpression);
499+
$query->from('appconfig')
500+
->setMaxResults(1);
501+
502+
$result = $query->executeQuery();
503+
$row = $result->fetchOne();
504+
$result->closeCursor();
505+
$this->assertEquals('yes', $row);
506+
}
507+
508+
public function testCaseWhenFalse(): void {
509+
$query = $this->connection->getQueryBuilder();
510+
511+
$caseExpression = $query->func()->caseWhen(
512+
$query->expr()->gt(new Literal(1), new Literal(3)),
513+
new Literal("'yes'"),
514+
new Literal("'no'")
515+
);
516+
$query->select($caseExpression);
517+
$query->from('appconfig')
518+
->setMaxResults(1);
519+
520+
$result = $query->executeQuery();
521+
$row = $result->fetchOne();
522+
$result->closeCursor();
523+
$this->assertEquals('no', $row);
524+
}
525+
526+
public function testCaseWhenWithAggregate(): void {
527+
$this->addIntDummyData(); // inserts editable = 1, 2, 3
528+
529+
$query = $this->connection->getQueryBuilder();
530+
531+
$effective = $query->func()->caseWhen(
532+
$query->expr()->neq('editable', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT)),
533+
new Literal(1),
534+
new Literal(50)
535+
);
536+
$query->select($query->func()->min($effective));
537+
$query->from('systemtag')
538+
->where($query->expr()->eq('name', $query->createNamedParameter('group_concat')));
539+
540+
$result = $query->executeQuery();
541+
$row = $result->fetchOne();
542+
$result->closeCursor();
543+
$this->assertEquals(1, $row);
544+
}
489545
}

0 commit comments

Comments
 (0)