diff --git a/3rdparty b/3rdparty index 489bcf4f81..2838fa6777 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit 489bcf4f81e462f4d74f0b76f58caeabd58e75de +Subproject commit 2838fa6777b1427c6c912a5e599a96639ac2b31f diff --git a/apps/dav/lib/Files/FileSearchBackend.php b/apps/dav/lib/Files/FileSearchBackend.php new file mode 100644 index 0000000000..2a89c756d3 --- /dev/null +++ b/apps/dav/lib/Files/FileSearchBackend.php @@ -0,0 +1,76 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Files; + +use OCA\DAV\Connector\Sabre\Directory; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Tree; +use SearchDAV\Backend\ISearchBackend; +use SearchDAV\Backend\SearchPropertyDefinition; + +class FileSearchBackend implements ISearchBackend { + /** @var Tree */ + private $tree; + + /** + * FileSearchBackend constructor. + * + * @param Tree $tree + */ + public function __construct(Tree $tree) { + $this->tree = $tree; + } + + /** + * Search endpoint will be remote.php/dav/files + * + * @return string + */ + public function getArbiterPath() { + return 'files'; + } + + public function isValidScope($href, $depth, $path) { + // only allow scopes inside the dav server + if (is_null($path)) { + return false; + } + + try { + $node = $this->tree->getNodeForPath($path); + return $node instanceof Directory; + } catch (NotFound $e) { + return false; + } + } + + public function getPropertyDefinitionsForScope($href, $path) { + // all valid scopes support the same schema + + return [ + new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), + new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), + new SearchPropertyDefinition('{DAV:}displayname', true, true, true), + new SearchPropertyDefinition('{http://ns.nextcloud.com:}fileid', false, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), + ]; + } +} diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php new file mode 100644 index 0000000000..60f4e484da --- /dev/null +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -0,0 +1,155 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\IMimeTypeLoader; +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchComparison; +use OCP\Files\Search\ISearchOperator; + +/** + * Tools for transforming search queries into database queries + */ +class QuerySearchUtil { + static protected $searchOperatorMap = [ + ISearchComparison::COMPARE_LIKE => 'iLike', + ISearchComparison::COMPARE_EQUAL => 'eq', + ISearchComparison::COMPARE_GREATER_THAN => 'gt', + ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte', + ISearchComparison::COMPARE_LESS_THAN => 'lt', + ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte' + ]; + + static protected $searchOperatorNegativeMap = [ + ISearchComparison::COMPARE_LIKE => 'notLike', + ISearchComparison::COMPARE_EQUAL => 'neq', + ISearchComparison::COMPARE_GREATER_THAN => 'lte', + ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt', + ISearchComparison::COMPARE_LESS_THAN => 'gte', + ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt' + ]; + + /** @var IMimeTypeLoader */ + private $mimetypeLoader; + + /** + * QuerySearchUtil constructor. + * + * @param IMimeTypeLoader $mimetypeLoader + */ + public function __construct(IMimeTypeLoader $mimetypeLoader) { + $this->mimetypeLoader = $mimetypeLoader; + } + + public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) { + $expr = $builder->expr(); + if ($operator instanceof ISearchBinaryOperator) { + switch ($operator->getType()) { + case ISearchBinaryOperator::OPERATOR_NOT: + $negativeOperator = $operator->getArguments()[0]; + if ($negativeOperator instanceof ISearchComparison) { + return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap); + } else { + throw new \InvalidArgumentException('Binary operators inside "not" is not supported'); + } + case ISearchBinaryOperator::OPERATOR_AND: + return $expr->andX($this->searchOperatorToDBExpr($operator->getArguments()[0], $operator->getArguments()[1])); + case ISearchBinaryOperator::OPERATOR_OR: + return $expr->orX($this->searchOperatorToDBExpr($operator->getArguments()[0], $operator->getArguments()[1])); + default: + throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType()); + } + } else if ($operator instanceof ISearchComparison) { + return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap); + } else { + throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator)); + } + } + + private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) { + if (!$this->isValidComparison($comparison)) { + throw new \InvalidArgumentException('Invalid comparison ' . $operator->getType() . ' on field ' . $operator->getField()); + } + + list($field, $value) = $this->getOperatorFieldAndValue($comparison); + if (isset($operatorMap[$comparison->getType()])) { + $queryOperator = $operatorMap[$comparison->getType()]; + return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value)); + } else { + throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType()); + } + } + + private function getOperatorFieldAndValue(ISearchComparison $operator) { + $field = $operator->getField(); + $value = $operator->getValue(); + if ($field === 'mimetype') { + if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) { + $value = $this->mimetypeLoader->getId($value); + } else if ($operator->getType() === ISearchComparison::COMPARE_LIKE) { + // transform "mimetype='foo/%'" to "mimepart='foo'" + if (preg_match('|(.+)/%|', $value, $matches)) { + $field = 'mimepart'; + $value = $this->mimetypeLoader->getId($matches[1]); + } + } + } + return [$field, $value]; + } + + private function isValidComparison(ISearchComparison $operator) { + $types = [ + 'mimetype' => 'string', + 'mtime' => \DateTime::class, + 'name' => 'string', + 'size' => 'integer' + ]; + $comparisons = [ + 'mimetype' => ['eq', 'like'], + 'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'], + 'name' => ['eq', 'like'], + 'size' => ['eq', 'gt', 'lt', 'gte', 'lte'] + ]; + + if (!isset($types[$operator->getField()])) { + return false; + } + $type = $types[$operator->getField()]; + if (gettype($operator->getValue()) !== $type && !(is_a($operator->getValue(), $type))) { + return false; + } + return in_array($operator->getType(), $comparisons[$operator->getField()]); + } + + private function getParameterForValue(IQueryBuilder $builder, $value) { + if ($value instanceof \DateTime) { + $value = $value->getTimestamp(); + } + if (is_numeric($value)) { + $type = IQueryBuilder::PARAM_INT; + } else { + $type = IQueryBuilder::PARAM_STR; + } + return $builder->createNamedParameter($value, $type); + } +} diff --git a/lib/private/Files/Search/SearchBinaryOperator.php b/lib/private/Files/Search/SearchBinaryOperator.php new file mode 100644 index 0000000000..15944e2776 --- /dev/null +++ b/lib/private/Files/Search/SearchBinaryOperator.php @@ -0,0 +1,20 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ diff --git a/lib/private/Files/Search/SearchComparison.php b/lib/private/Files/Search/SearchComparison.php new file mode 100644 index 0000000000..32c4ad0e5a --- /dev/null +++ b/lib/private/Files/Search/SearchComparison.php @@ -0,0 +1,67 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchComparison; + +class SearchComparison implements ISearchComparison { + /** @var string */ + private $type; + /** @var string */ + private $field; + /** @var string|integer|\DateTime */ + private $value; + + /** + * SearchComparison constructor. + * + * @param string $type + * @param string $field + * @param \DateTime|int|string $value + */ + public function __construct($type, $field, $value) { + $this->type = $type; + $this->field = $field; + $this->value = $value; + } + + /** + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * @return string + */ + public function getField() { + return $this->field; + } + + /** + * @return \DateTime|int|string + */ + public function getValue() { + return $this->value; + } +} diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php new file mode 100644 index 0000000000..c76d6f2e25 --- /dev/null +++ b/lib/private/Files/Search/SearchOrder.php @@ -0,0 +1,57 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Search; + + +use OCP\Files\Search\ISearchOrder; + +class SearchOrder implements ISearchOrder { + /** @var string */ + private $direction; + /** @var string */ + private $field; + + /** + * SearchOrder constructor. + * + * @param string $direction + * @param string $field + */ + public function __construct($direction, $field) { + $this->direction = $direction; + $this->field = $field; + } + + /** + * @return string + */ + public function getDirection() { + return $this->direction; + } + + /** + * @return string + */ + public function getField() { + return $this->field; + } +} diff --git a/lib/private/Files/Search/SearchQuery.php b/lib/private/Files/Search/SearchQuery.php new file mode 100644 index 0000000000..a017d3e245 --- /dev/null +++ b/lib/private/Files/Search/SearchQuery.php @@ -0,0 +1,23 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Search; + diff --git a/lib/public/Files/Search/ISearchBinaryOperator.php b/lib/public/Files/Search/ISearchBinaryOperator.php new file mode 100644 index 0000000000..248e3de56b --- /dev/null +++ b/lib/public/Files/Search/ISearchBinaryOperator.php @@ -0,0 +1,46 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Files\Search; + +interface ISearchBinaryOperator extends ISearchOperator { + const OPERATOR_AND = 'and'; + const OPERATOR_OR = 'or'; + const OPERATOR_NOT = 'not'; + + /** + * The type of binary operator + * + * One of the ISearchBinaryOperator::OPERATOR_* constants + * + * @return string + */ + public function getType(); + + /** + * The arguments for the binary operator + * + * One argument for the 'not' operator and two for 'and' and 'or' + * + * @return ISearchOperator[] + */ + public function getArguments(); +} diff --git a/lib/public/Files/Search/ISearchComparison.php b/lib/public/Files/Search/ISearchComparison.php new file mode 100644 index 0000000000..0534221861 --- /dev/null +++ b/lib/public/Files/Search/ISearchComparison.php @@ -0,0 +1,54 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Files\Search; + +interface ISearchComparison extends ISearchOperator { + const COMPARE_EQUAL = 'eq'; + const COMPARE_GREATER_THAN = 'gt'; + const COMPARE_GREATER_THAN_EQUAL = 'gte'; + const COMPARE_LESS_THAN = 'lt'; + const COMPARE_LESS_THAN_EQUAL = 'lte'; + const COMPARE_LIKE = 'like'; + + /** + * Get the type of comparison, one of the ISearchComparison::COMPARE_* constants + * + * @return string + */ + public function getType(); + + /** + * Get the name of the field to compare with + * + * i.e. 'size', 'name' or 'mimetype' + * + * @return string + */ + public function getField(); + + /** + * Get the value to compare the field with + * + * @return string|integer|\DateTime + */ + public function getValue(); +} diff --git a/lib/public/Files/Search/ISearchOperator.php b/lib/public/Files/Search/ISearchOperator.php new file mode 100644 index 0000000000..fc1db57f4c --- /dev/null +++ b/lib/public/Files/Search/ISearchOperator.php @@ -0,0 +1,26 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Files\Search; + +interface ISearchCondition { + +} diff --git a/lib/public/Files/Search/ISearchOrder.php b/lib/public/Files/Search/ISearchOrder.php new file mode 100644 index 0000000000..1abfd7506d --- /dev/null +++ b/lib/public/Files/Search/ISearchOrder.php @@ -0,0 +1,46 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Files\Search; + +/** + * @since 12.0.0 + */ +interface ISearchOrder { + const DIRECTION_ASCENDING = 'asc'; + const DIRECTION_DESCENDING = 'desc'; + + /** + * The direction to sort in, either ISearchOrder::DIRECTION_ASCENDING or ISearchOrder::DIRECTION_DESCENDING + * + * @return string + * @since 12.0.0 + */ + public function getDirection(); + + /** + * The field to sort on + * + * @return string + * @since 12.0.0 + */ + public function getField(); +} diff --git a/lib/public/Files/Search/ISearchQuery.php b/lib/public/Files/Search/ISearchQuery.php new file mode 100644 index 0000000000..f9c07533a1 --- /dev/null +++ b/lib/public/Files/Search/ISearchQuery.php @@ -0,0 +1,33 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Files\Search; + +/** + * @since 12.0.0 + */ +interface ISearchQuery { + /** + * @return ISearchOperator + * @since 12.0.0 + */ + public function getSearchOperation(); +} diff --git a/tests/lib/Files/Cache/QuerySearchHelperTest.php b/tests/lib/Files/Cache/QuerySearchHelperTest.php new file mode 100644 index 0000000000..bdf3d79432 --- /dev/null +++ b/tests/lib/Files/Cache/QuerySearchHelperTest.php @@ -0,0 +1,28 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Test\Files\Cache; + +use Test\TestCase; + +class QuerySearchHelperTest extends TestCase { + +}