Skip to content

Commit fcafb9e

Browse files
committed
add some comments for the distributive operation and add another test
Signed-off-by: Robin Appelman <robin@icewind.nl>
1 parent 1c87cee commit fcafb9e

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

lib/private/Files/Search/QueryOptimizer/MergeDistributiveOperations.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,41 @@
1010
/**
1111
* Attempt to transform
1212
*
13-
* (A AND B) OR (A AND C) into A AND (B OR C)
13+
* (A AND B) OR (A AND C) OR (A AND D AND E) into A AND (B OR C OR (D AND E))
14+
*
15+
* This is always valid because logical 'AND' and 'OR' are distributive[1].
16+
*
17+
* [1]: https://en.wikipedia.org/wiki/Distributive_property
1418
*/
1519
class MergeDistributiveOperations extends ReplacingOptimizerStep {
1620
public function processOperator(ISearchOperator &$operator): bool {
1721
if (
1822
$operator instanceof SearchBinaryOperator &&
1923
$this->isAllSameBinaryOperation($operator->getArguments())
2024
) {
25+
// either 'AND' or 'OR'
2126
$topLevelType = $operator->getType();
2227

28+
// split the arguments into groups that share a first argument
29+
// (we already know that all arguments are binary operators with at least 1 child)
2330
$groups = $this->groupBinaryOperatorsByChild($operator->getArguments(), 0);
2431
$outerOperations = array_map(function (array $operators) use ($topLevelType) {
32+
// no common operations, no need to change anything
2533
if (count($operators) === 1) {
2634
return $operators[0];
2735
}
2836
/** @var ISearchBinaryOperator $firstArgument */
2937
$firstArgument = $operators[0];
30-
$outerType = $firstArgument->getType();
38+
39+
// we already checked that all arguments have the same type, so this type applies for all, either 'AND' or 'OR'
40+
$innerType = $firstArgument->getType();
41+
42+
// the common operation we move out ('A' from the example)
3143
$extractedLeftHand = $firstArgument->getArguments()[0];
3244

45+
// for each argument we remove the extracted operation to get the leftovers ('B', 'C' and '(D AND E)' in the example)
46+
// note that we leave them inside the "inner" binary operation for when the "inner" operation contained more than two parts
47+
// in the (common) case where the "inner" operation only has 1 item left it will be cleaned up in a follow up step
3348
$rightHandArguments = array_map(function (ISearchOperator $inner) {
3449
/** @var ISearchBinaryOperator $inner */
3550
$arguments = $inner->getArguments();
@@ -39,12 +54,17 @@ public function processOperator(ISearchOperator &$operator): bool {
3954
}
4055
return new SearchBinaryOperator($inner->getType(), $arguments);
4156
}, $operators);
57+
58+
// combine the extracted operation ('A') with the remaining bit ('(B OR C OR (D AND E))')
59+
// note that because of how distribution work, we use the "outer" type "inside" and the "inside" type "outside".
4260
$extractedRightHand = new SearchBinaryOperator($topLevelType, $rightHandArguments);
4361
return new SearchBinaryOperator(
44-
$outerType,
62+
$innerType,
4563
[$extractedLeftHand, $extractedRightHand]
4664
);
4765
}, $groups);
66+
67+
// combine all groups again
4868
$operator = new SearchBinaryOperator($topLevelType, $outerOperations);
4969
parent::processOperator($operator);
5070
return true;
@@ -90,7 +110,6 @@ private function groupBinaryOperatorsByChild(array $operators, int $index = 0):
90110
foreach ($operators as $operator) {
91111
/** @var SearchBinaryOperator|SearchComparison $child */
92112
$child = $operator->getArguments()[$index];
93-
;
94113
$childKey = (string) $child;
95114
$result[$childKey][] = $operator;
96115
}

tests/lib/Files/Search/QueryOptimizer/MergeDistributiveOperationsTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,31 @@ public function testOptimizeInside() {
130130

131131
$this->assertEquals('((storage eq 1 and (path eq "foo" or path eq "bar" or path eq "asd")) and mimetype eq "text")', $operator->__toString());
132132
}
133+
134+
public function testMoveInnerOperations() {
135+
$operator = new SearchBinaryOperator(
136+
ISearchBinaryOperator::OPERATOR_OR,
137+
[
138+
new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
139+
new SearchComparison(ISearchComparison::COMPARE_EQUAL, "storage", 1),
140+
new SearchComparison(ISearchComparison::COMPARE_EQUAL, "path", "foo"),
141+
]),
142+
new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
143+
new SearchComparison(ISearchComparison::COMPARE_EQUAL, "storage", 1),
144+
new SearchComparison(ISearchComparison::COMPARE_EQUAL, "path", "bar"),
145+
]),
146+
new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
147+
new SearchComparison(ISearchComparison::COMPARE_EQUAL, "storage", 1),
148+
new SearchComparison(ISearchComparison::COMPARE_EQUAL, "path", "asd"),
149+
new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, "size", "100"),
150+
])
151+
]
152+
);
153+
$this->assertEquals('((storage eq 1 and path eq "foo") or (storage eq 1 and path eq "bar") or (storage eq 1 and path eq "asd" and size gt "100"))', $operator->__toString());
154+
155+
$this->optimizer->processOperator($operator);
156+
$this->simplifier->processOperator($operator);
157+
158+
$this->assertEquals('(storage eq 1 and (path eq "foo" or path eq "bar" or (path eq "asd" and size gt "100")))', $operator->__toString());
159+
}
133160
}

0 commit comments

Comments
 (0)