Skip to content

Commit 7cf651e

Browse files
committed
split of query building bits from searchhelper
Signed-off-by: Robin Appelman <[email protected]>
1 parent 139fac9 commit 7cf651e

File tree

3 files changed

+251
-216
lines changed

3 files changed

+251
-216
lines changed

lib/private/Files/Cache/QuerySearchHelper.php

Lines changed: 7 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -28,41 +28,15 @@
2828

2929
use OC\Files\Search\SearchBinaryOperator;
3030
use OC\SystemConfig;
31-
use OCP\DB\QueryBuilder\IQueryBuilder;
3231
use OCP\Files\Cache\ICache;
3332
use OCP\Files\Cache\ICacheEntry;
3433
use OCP\Files\IMimeTypeLoader;
3534
use OCP\Files\Search\ISearchBinaryOperator;
36-
use OCP\Files\Search\ISearchComparison;
37-
use OCP\Files\Search\ISearchOperator;
38-
use OCP\Files\Search\ISearchOrder;
3935
use OCP\Files\Search\ISearchQuery;
4036
use OCP\IDBConnection;
4137
use OCP\ILogger;
4238

43-
/**
44-
* Tools for transforming search queries into database queries
45-
*/
4639
class QuerySearchHelper {
47-
protected static $searchOperatorMap = [
48-
ISearchComparison::COMPARE_LIKE => 'iLike',
49-
ISearchComparison::COMPARE_EQUAL => 'eq',
50-
ISearchComparison::COMPARE_GREATER_THAN => 'gt',
51-
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
52-
ISearchComparison::COMPARE_LESS_THAN => 'lt',
53-
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte',
54-
];
55-
56-
protected static $searchOperatorNegativeMap = [
57-
ISearchComparison::COMPARE_LIKE => 'notLike',
58-
ISearchComparison::COMPARE_EQUAL => 'neq',
59-
ISearchComparison::COMPARE_GREATER_THAN => 'lte',
60-
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
61-
ISearchComparison::COMPARE_LESS_THAN => 'gte',
62-
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt',
63-
];
64-
65-
public const TAG_FAVORITE = '_$!<Favorite>!$_';
6640

6741
/** @var IMimeTypeLoader */
6842
private $mimetypeLoader;
@@ -72,6 +46,8 @@ class QuerySearchHelper {
7246
private $systemConfig;
7347
/** @var ILogger */
7448
private $logger;
49+
/** @var SearchBuilder */
50+
private $searchBuilder;
7551

7652
public function __construct(
7753
IMimeTypeLoader $mimetypeLoader,
@@ -83,172 +59,7 @@ public function __construct(
8359
$this->connection = $connection;
8460
$this->systemConfig = $systemConfig;
8561
$this->logger = $logger;
86-
}
87-
88-
/**
89-
* Whether or not the tag tables should be joined to complete the search
90-
*
91-
* @param ISearchOperator $operator
92-
* @return boolean
93-
*/
94-
public function shouldJoinTags(ISearchOperator $operator) {
95-
if ($operator instanceof ISearchBinaryOperator) {
96-
return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
97-
return $shouldJoin || $this->shouldJoinTags($operator);
98-
}, false);
99-
} elseif ($operator instanceof ISearchComparison) {
100-
return $operator->getField() === 'tagname' || $operator->getField() === 'favorite';
101-
}
102-
return false;
103-
}
104-
105-
/**
106-
* @param IQueryBuilder $builder
107-
* @param ISearchOperator $operator
108-
*/
109-
public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
110-
return array_filter(array_map(function ($operator) use ($builder) {
111-
return $this->searchOperatorToDBExpr($builder, $operator);
112-
}, $operators));
113-
}
114-
115-
public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
116-
$expr = $builder->expr();
117-
if ($operator instanceof ISearchBinaryOperator) {
118-
if (count($operator->getArguments()) === 0) {
119-
return null;
120-
}
121-
122-
switch ($operator->getType()) {
123-
case ISearchBinaryOperator::OPERATOR_NOT:
124-
$negativeOperator = $operator->getArguments()[0];
125-
if ($negativeOperator instanceof ISearchComparison) {
126-
return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
127-
} else {
128-
throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
129-
}
130-
// no break
131-
case ISearchBinaryOperator::OPERATOR_AND:
132-
return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
133-
case ISearchBinaryOperator::OPERATOR_OR:
134-
return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
135-
default:
136-
throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
137-
}
138-
} elseif ($operator instanceof ISearchComparison) {
139-
return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
140-
} else {
141-
throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
142-
}
143-
}
144-
145-
private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) {
146-
$this->validateComparison($comparison);
147-
148-
[$field, $value, $type] = $this->getOperatorFieldAndValue($comparison);
149-
if (isset($operatorMap[$type])) {
150-
$queryOperator = $operatorMap[$type];
151-
return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
152-
} else {
153-
throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType());
154-
}
155-
}
156-
157-
private function getOperatorFieldAndValue(ISearchComparison $operator) {
158-
$field = $operator->getField();
159-
$value = $operator->getValue();
160-
$type = $operator->getType();
161-
if ($field === 'mimetype') {
162-
if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
163-
$value = (int)$this->mimetypeLoader->getId($value);
164-
} elseif ($operator->getType() === ISearchComparison::COMPARE_LIKE) {
165-
// transform "mimetype='foo/%'" to "mimepart='foo'"
166-
if (preg_match('|(.+)/%|', $value, $matches)) {
167-
$field = 'mimepart';
168-
$value = (int)$this->mimetypeLoader->getId($matches[1]);
169-
$type = ISearchComparison::COMPARE_EQUAL;
170-
} elseif (strpos($value, '%') !== false) {
171-
throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
172-
} else {
173-
$field = 'mimetype';
174-
$value = (int)$this->mimetypeLoader->getId($value);
175-
$type = ISearchComparison::COMPARE_EQUAL;
176-
}
177-
}
178-
} elseif ($field === 'favorite') {
179-
$field = 'tag.category';
180-
$value = self::TAG_FAVORITE;
181-
} elseif ($field === 'tagname') {
182-
$field = 'tag.category';
183-
} elseif ($field === 'fileid') {
184-
$field = 'file.fileid';
185-
} elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL) {
186-
$field = 'path_hash';
187-
$value = md5((string)$value);
188-
}
189-
return [$field, $value, $type];
190-
}
191-
192-
private function validateComparison(ISearchComparison $operator) {
193-
$types = [
194-
'mimetype' => 'string',
195-
'mtime' => 'integer',
196-
'name' => 'string',
197-
'path' => 'string',
198-
'size' => 'integer',
199-
'tagname' => 'string',
200-
'favorite' => 'boolean',
201-
'fileid' => 'integer',
202-
'storage' => 'integer',
203-
];
204-
$comparisons = [
205-
'mimetype' => ['eq', 'like'],
206-
'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
207-
'name' => ['eq', 'like'],
208-
'path' => ['eq', 'like'],
209-
'size' => ['eq', 'gt', 'lt', 'gte', 'lte'],
210-
'tagname' => ['eq', 'like'],
211-
'favorite' => ['eq'],
212-
'fileid' => ['eq'],
213-
'storage' => ['eq'],
214-
];
215-
216-
if (!isset($types[$operator->getField()])) {
217-
throw new \InvalidArgumentException('Unsupported comparison field ' . $operator->getField());
218-
}
219-
$type = $types[$operator->getField()];
220-
if (gettype($operator->getValue()) !== $type) {
221-
throw new \InvalidArgumentException('Invalid type for field ' . $operator->getField());
222-
}
223-
if (!in_array($operator->getType(), $comparisons[$operator->getField()])) {
224-
throw new \InvalidArgumentException('Unsupported comparison for field ' . $operator->getField() . ': ' . $operator->getType());
225-
}
226-
}
227-
228-
private function getParameterForValue(IQueryBuilder $builder, $value) {
229-
if ($value instanceof \DateTime) {
230-
$value = $value->getTimestamp();
231-
}
232-
if (is_numeric($value)) {
233-
$type = IQueryBuilder::PARAM_INT;
234-
} else {
235-
$type = IQueryBuilder::PARAM_STR;
236-
}
237-
return $builder->createNamedParameter($value, $type);
238-
}
239-
240-
/**
241-
* @param IQueryBuilder $query
242-
* @param ISearchOrder[] $orders
243-
*/
244-
public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
245-
foreach ($orders as $order) {
246-
$field = $order->getField();
247-
if ($field === 'fileid') {
248-
$field = 'file.fileid';
249-
}
250-
$query->addOrderBy($field, $order->getDirection());
251-
}
62+
$this->searchBuilder = new SearchBuilder($this->mimetypeLoader);
25263
}
25364

25465
protected function getQueryBuilder() {
@@ -289,7 +100,7 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array
289100

290101
$query = $builder->selectFileCache('file');
291102

292-
if ($this->shouldJoinTags($searchQuery->getSearchOperation())) {
103+
if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) {
293104
$user = $searchQuery->getUser();
294105
if ($user === null) {
295106
throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query");
@@ -304,17 +115,17 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array
304115
->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID())));
305116
}
306117

307-
$searchExpr = $this->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
118+
$searchExpr = $this->searchBuilder->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
308119
if ($searchExpr) {
309120
$query->andWhere($searchExpr);
310121
}
311122

312123
$storageFilters = array_values(array_map(function (ICache $cache) {
313124
return $cache->getQueryFilterForStorage();
314125
}, $caches));
315-
$query->andWhere($this->searchOperatorToDBExpr($builder, new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters)));
126+
$query->andWhere($this->searchBuilder->searchOperatorToDBExpr($builder, new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters)));
316127

317-
$this->addSearchOrdersToQuery($query, $searchQuery->getOrder());
128+
$this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
318129

319130
if ($searchQuery->getLimit()) {
320131
$query->setMaxResults($searchQuery->getLimit());

0 commit comments

Comments
 (0)