Completed
Push — master ( 74ac5d...2a8e92 )
by Robin
24:33
created

QuerySearchHelper::searchOperatorToDBExpr()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 24
Code Lines 20

Duplication

Lines 4
Ratio 16.67 %

Importance

Changes 0
Metric Value
cc 7
eloc 20
nc 7
nop 2
dl 4
loc 24
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Robin Appelman <[email protected]>
4
 *
5
 * @license GNU AGPL version 3 or any later version
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as
9
 * published by the Free Software Foundation, either version 3 of the
10
 * License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 *
20
 */
21
22
namespace OC\Files\Cache;
23
24
use OCP\DB\QueryBuilder\IQueryBuilder;
25
use OCP\Files\IMimeTypeLoader;
26
use OCP\Files\Search\ISearchBinaryOperator;
27
use OCP\Files\Search\ISearchComparison;
28
use OCP\Files\Search\ISearchOperator;
29
30
/**
31
 * Tools for transforming search queries into database queries
32
 */
33
class QuerySearchHelper {
34
	static protected $searchOperatorMap = [
35
		ISearchComparison::COMPARE_LIKE => 'iLike',
36
		ISearchComparison::COMPARE_EQUAL => 'eq',
37
		ISearchComparison::COMPARE_GREATER_THAN => 'gt',
38
		ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
39
		ISearchComparison::COMPARE_LESS_THAN => 'lt',
40
		ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte'
41
	];
42
43
	static protected $searchOperatorNegativeMap = [
44
		ISearchComparison::COMPARE_LIKE => 'notLike',
45
		ISearchComparison::COMPARE_EQUAL => 'neq',
46
		ISearchComparison::COMPARE_GREATER_THAN => 'lte',
47
		ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
48
		ISearchComparison::COMPARE_LESS_THAN => 'gte',
49
		ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt'
50
	];
51
52
	/** @var IMimeTypeLoader */
53
	private $mimetypeLoader;
54
55
	/**
56
	 * QuerySearchUtil constructor.
57
	 *
58
	 * @param IMimeTypeLoader $mimetypeLoader
59
	 */
60
	public function __construct(IMimeTypeLoader $mimetypeLoader) {
61
		$this->mimetypeLoader = $mimetypeLoader;
62
	}
63
64
	public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
65
		$expr = $builder->expr();
66
		if ($operator instanceof ISearchBinaryOperator) {
67
			switch ($operator->getType()) {
68
				case ISearchBinaryOperator::OPERATOR_NOT:
69
					$negativeOperator = $operator->getArguments()[0];
70
					if ($negativeOperator instanceof ISearchComparison) {
71
						return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
72
					} else {
73
						throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
74
					}
75 View Code Duplication
				case ISearchBinaryOperator::OPERATOR_AND:
76
					return $expr->andX($this->searchOperatorToDBExpr($builder, $operator->getArguments()[0]), $this->searchOperatorToDBExpr($builder, $operator->getArguments()[1]));
77 View Code Duplication
				case ISearchBinaryOperator::OPERATOR_OR:
78
					return $expr->orX($this->searchOperatorToDBExpr($builder, $operator->getArguments()[0]), $this->searchOperatorToDBExpr($builder, $operator->getArguments()[1]));
79
				default:
80
					throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
81
			}
82
		} else if ($operator instanceof ISearchComparison) {
83
			return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
84
		} else {
85
			throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
86
		}
87
	}
88
89
	private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) {
90
		$this->validateComparison($comparison);
91
92
		list($field, $value, $type) = $this->getOperatorFieldAndValue($comparison);
93
		if (isset($operatorMap[$type])) {
94
			$queryOperator = $operatorMap[$type];
95
			return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
96
		} else {
97
			throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType());
98
		}
99
	}
100
101
	private function getOperatorFieldAndValue(ISearchComparison $operator) {
102
		$field = $operator->getField();
103
		$value = $operator->getValue();
104
		$type = $operator->getType();
105
		if ($field === 'mimetype') {
106
			if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
107
				$value = $this->mimetypeLoader->getId($value);
108
			} else if ($operator->getType() === ISearchComparison::COMPARE_LIKE) {
109
				// transform "mimetype='foo/%'" to "mimepart='foo'"
110
				if (preg_match('|(.+)/%|', $value, $matches)) {
111
					$field = 'mimepart';
112
					$value = $this->mimetypeLoader->getId($matches[1]);
113
					$type = ISearchComparison::COMPARE_EQUAL;
114
				}
115
				if (strpos($value, '%') !== false) {
116
					throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
117
				}
118
			}
119
		}
120
		return [$field, $value, $type];
121
	}
122
123
	private function validateComparison(ISearchComparison $operator) {
124
		$types = [
125
			'mimetype' => 'string',
126
			'mtime' => 'integer',
127
			'name' => 'string',
128
			'size' => 'integer'
129
		];
130
		$comparisons = [
131
			'mimetype' => ['eq', 'like'],
132
			'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
133
			'name' => ['eq', 'like'],
134
			'size' => ['eq', 'gt', 'lt', 'gte', 'lte']
135
		];
136
137
		if (!isset($types[$operator->getField()])) {
138
			throw new \InvalidArgumentException('Unsupported comparison field ' . $operator->getField());
139
		}
140
		$type = $types[$operator->getField()];
141
		if (gettype($operator->getValue()) !== $type) {
142
			throw new \InvalidArgumentException('Invalid type for field ' . $operator->getField());
143
		}
144
		if (!in_array($operator->getType(), $comparisons[$operator->getField()])) {
145
			throw new \InvalidArgumentException('Unsupported comparison for field  ' . $operator->getField() . ': ' . $operator->getType());
146
		}
147
	}
148
149
	private function getParameterForValue(IQueryBuilder $builder, $value) {
150
		if ($value instanceof \DateTime) {
151
			$value = $value->getTimestamp();
152
		}
153
		if (is_numeric($value)) {
154
			$type = IQueryBuilder::PARAM_INT;
155
		} else {
156
			$type = IQueryBuilder::PARAM_STR;
157
		}
158
		return $builder->createNamedParameter($value, $type);
159
	}
160
}
161