Passed
Push — master ( 4c60ff...437d93 )
by Julius
15:25 queued 12s
created

QuerySearchHelper::searchInCaches()   B

Complexity

Conditions 9
Paths 65

Size

Total Lines 74
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 38
nc 65
nop 2
dl 0
loc 74
rs 7.7564
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Robin Appelman <[email protected]>
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Robin Appelman <[email protected]>
7
 * @author Roeland Jago Douma <[email protected]>
8
 * @author Tobias Kaminsky <[email protected]>
9
 *
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
namespace OC\Files\Cache;
27
28
use OC\Files\Search\SearchBinaryOperator;
29
use OC\SystemConfig;
30
use OCP\Files\Cache\ICache;
31
use OCP\Files\Cache\ICacheEntry;
32
use OCP\Files\IMimeTypeLoader;
33
use OCP\Files\Search\ISearchBinaryOperator;
34
use OCP\Files\Search\ISearchQuery;
35
use OCP\IDBConnection;
36
use OCP\ILogger;
37
38
class QuerySearchHelper {
39
40
	/** @var IMimeTypeLoader */
41
	private $mimetypeLoader;
42
	/** @var IDBConnection */
43
	private $connection;
44
	/** @var SystemConfig */
45
	private $systemConfig;
46
	/** @var ILogger */
47
	private $logger;
48
	/** @var SearchBuilder */
49
	private $searchBuilder;
50
51
	public function __construct(
52
		IMimeTypeLoader $mimetypeLoader,
53
		IDBConnection $connection,
54
		SystemConfig $systemConfig,
55
		ILogger $logger,
56
		SearchBuilder $searchBuilder
57
	) {
58
		$this->mimetypeLoader = $mimetypeLoader;
59
		$this->connection = $connection;
60
		$this->systemConfig = $systemConfig;
61
		$this->logger = $logger;
62
		$this->searchBuilder = $searchBuilder;
63
	}
64
65
	protected function getQueryBuilder() {
66
		return new CacheQueryBuilder(
67
			$this->connection,
68
			$this->systemConfig,
69
			$this->logger
70
		);
71
	}
72
73
	/**
74
	 * Perform a file system search in multiple caches
75
	 *
76
	 * the results will be grouped by the same array keys as the $caches argument to allow
77
	 * post-processing based on which cache the result came from
78
	 *
79
	 * @template T of array-key
80
	 * @param ISearchQuery $searchQuery
81
	 * @param array<T, ICache> $caches
82
	 * @return array<T, ICacheEntry[]>
83
	 */
84
	public function searchInCaches(ISearchQuery $searchQuery, array $caches): array {
85
		// search in multiple caches at once by creating one query in the following format
86
		// SELECT ... FROM oc_filecache WHERE
87
		//     <filter expressions from the search query>
88
		// AND (
89
		//     <filter expression for storage1> OR
90
		//     <filter expression for storage2> OR
91
		//     ...
92
		// );
93
		//
94
		// This gives us all the files matching the search query from all caches
95
		//
96
		// while the resulting rows don't have a way to tell what storage they came from (multiple storages/caches can share storage_id)
97
		// we can just ask every cache if the row belongs to them and give them the cache to do any post processing on the result.
98
99
		$builder = $this->getQueryBuilder();
100
101
		$query = $builder->selectFileCache('file');
102
103
		if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) {
104
			$user = $searchQuery->getUser();
105
			if ($user === null) {
106
				throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query");
107
			}
108
			$query
109
				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
110
				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
111
					$builder->expr()->eq('tagmap.type', 'tag.type'),
112
					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
113
				))
114
				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
115
				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID())));
116
		}
117
118
		$searchExpr = $this->searchBuilder->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
119
		if ($searchExpr) {
120
			$query->andWhere($searchExpr);
121
		}
122
123
		$storageFilters = array_values(array_map(function (ICache $cache) {
124
			return $cache->getQueryFilterForStorage();
125
		}, $caches));
126
		$query->andWhere($this->searchBuilder->searchOperatorToDBExpr($builder, new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters)));
127
128
		$this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
129
130
		if ($searchQuery->getLimit()) {
131
			$query->setMaxResults($searchQuery->getLimit());
132
		}
133
		if ($searchQuery->getOffset()) {
134
			$query->setFirstResult($searchQuery->getOffset());
135
		}
136
137
		$result = $query->execute();
138
		$files = $result->fetchAll();
139
140
		$rawEntries = array_map(function (array $data) {
141
			return Cache::cacheEntryFromData($data, $this->mimetypeLoader);
142
		}, $files);
143
144
		$result->closeCursor();
145
146
		// loop trough all caches for each result to see if the result matches that storage
147
		// results are grouped by the same array keys as the caches argument to allow the caller to distringuish the source of the results
148
		$results = array_fill_keys(array_keys($caches), []);
149
		foreach ($rawEntries as $rawEntry) {
150
			foreach ($caches as $cacheKey => $cache) {
151
				$entry = $cache->getCacheEntryFromSearchResult($rawEntry);
152
				if ($entry) {
153
					$results[$cacheKey][] = $entry;
154
				}
155
			}
156
		}
157
		return $results;
158
	}
159
}
160