2828
2929use OC \Files \Search \SearchBinaryOperator ;
3030use OC \SystemConfig ;
31- use OCP \DB \QueryBuilder \IQueryBuilder ;
3231use OCP \Files \Cache \ICache ;
3332use OCP \Files \Cache \ICacheEntry ;
3433use OCP \Files \IMimeTypeLoader ;
3534use OCP \Files \Search \ISearchBinaryOperator ;
36- use OCP \Files \Search \ISearchComparison ;
37- use OCP \Files \Search \ISearchOperator ;
38- use OCP \Files \Search \ISearchOrder ;
3935use OCP \Files \Search \ISearchQuery ;
4036use OCP \IDBConnection ;
4137use OCP \ILogger ;
4238
43- /**
44- * Tools for transforming search queries into database queries
45- */
4639class 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