Skip to content

Commit

Permalink
Merge pull request #41459 from nextcloud/enh/noid/search-metadata-null
Browse files Browse the repository at this point in the history
implements search on null/notnull metadata
  • Loading branch information
ArtificialOwl authored Nov 23, 2023
2 parents f1a7fce + fbe92d4 commit ee787cd
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 14 deletions.
4 changes: 2 additions & 2 deletions apps/dav/lib/CalDAV/CachedSubscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ public function getMultipleChildren(array $paths):array {

/**
* @param string $name
* @param null|resource|string $calendarData
* @param null|resource|string $data
* @return null|string
* @throws MethodNotAllowed
*/
public function createFile($name, $calendarData = null) {
public function createFile($name, $data = null) {
throw new MethodNotAllowed('Creating objects in cached subscription is not allowed');
}

Expand Down
31 changes: 22 additions & 9 deletions apps/dav/lib/Files/FileSearchBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchOrder;
use OCP\Files\Search\ISearchQuery;
Expand Down Expand Up @@ -367,22 +368,30 @@ private function transformSearchOperation(Operator $operator) {
if (count($operator->arguments) !== 2) {
throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation');
}
if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) {
throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
}
if (!($operator->arguments[1] instanceof Literal)) {
throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
}

$value = $operator->arguments[1]->value;
case Operator::OPERATION_IS_DEFINED:
if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) {
throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
}
$property = $operator->arguments[0];
$value = $this->castValue($property, $operator->arguments[1]->value);

if (str_starts_with($property->name, FilesPlugin::FILE_METADATA_PREFIX)) {
return new SearchComparison($trimmedType, substr($property->name, strlen(FilesPlugin::FILE_METADATA_PREFIX)), $value, IMetadataQuery::EXTRA);
$field = substr($property->name, strlen(FilesPlugin::FILE_METADATA_PREFIX));
$extra = IMetadataQuery::EXTRA;
} else {
return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($property), $value);
$field = $this->mapPropertyNameToColumn($property);
}

// no break
return new SearchComparison(
$trimmedType,
$field,
$this->castValue($property, $value ?? ''),
$extra ?? ''
);

case Operator::OPERATION_IS_COLLECTION:
return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
default:
Expand Down Expand Up @@ -416,7 +425,11 @@ private function mapPropertyNameToColumn(SearchPropertyDefinition $property) {
}

private function castValue(SearchPropertyDefinition $property, $value) {
switch ($property->dataType) {
if ($value === '') {
return '';
}

switch ($property->dataType) {
case SearchPropertyDefinition::DATATYPE_BOOLEAN:
return $value === 'yes';
case SearchPropertyDefinition::DATATYPE_DECIMAL:
Expand Down
36 changes: 34 additions & 2 deletions build/integration/features/bootstrap/CommentsContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,37 @@ public function __construct($baseUrl) {
}
}



/**
* get a named entry from response instead of picking a random entry from values
*
* @param string $path
*
* @return array|string
* @throws Exception
*/
private function getValueFromNamedEntries(string $path, array $response): mixed {
$next = '';
if (str_contains($path, ' ')) {
[$key, $next] = explode(' ', $path, 2);
} else {
$key = $path;
}

foreach ($response as $entry) {
if ($entry['name'] === $key) {
if ($next !== '') {
return $this->getValueFromNamedEntries($next, $entry['value']);
} else {
return $entry['value'];
}
}
}

return null;
}

/** @AfterScenario */
public function teardownScenario() {
$client = new \GuzzleHttp\Client();
Expand Down Expand Up @@ -175,7 +206,7 @@ public function asLoadloadAllTheCommentsOfTheFileNamedItShouldReturn($user, $fil
if ($res->getStatusCode() === 207) {
$service = new Sabre\Xml\Service();
$this->response = $service->parse($res->getBody()->getContents());
$this->commentId = (int) ($this->response[0]['value'][2]['value'][0]['value'][0]['value'] ?? 0);
$this->commentId = (int) ($this->getValueFromNamedEntries('{DAV:}response {DAV:}propstat {DAV:}prop {http://owncloud.org/ns}id', $this->response ?? []) ?? 0);
}
}

Expand Down Expand Up @@ -238,7 +269,8 @@ public function asDeleteTheCreatedCommentItShouldReturn($user, $statusCode) {
* @throws \Exception
*/
public function theResponseShouldContainAPropertyWithValue($key, $value) {
$keys = $this->response[0]['value'][2]['value'][0]['value'];
// $keys = $this->response[0]['value'][1]['value'][0]['value'];
$keys = $this->getValueFromNamedEntries('{DAV:}response {DAV:}propstat {DAV:}prop', $this->response);
$found = false;
foreach ($keys as $singleKey) {
if ($singleKey['name'] === '{http://owncloud.org/ns}' . substr($key, 3)) {
Expand Down
2 changes: 2 additions & 0 deletions lib/private/Files/Cache/SearchBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
ISearchComparison::COMPARE_LESS_THAN => 'lt',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte',
ISearchComparison::COMPARE_DEFINED => 'isNotNull',
];

protected static $searchOperatorNegativeMap = [
Expand All @@ -57,6 +58,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
ISearchComparison::COMPARE_LESS_THAN => 'gte',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'gt',
ISearchComparison::COMPARE_DEFINED => 'isNull',
];

public const TAG_FAVORITE = '_$!<Favorite>!$_';
Expand Down
1 change: 1 addition & 0 deletions lib/public/Files/Search/ISearchComparison.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface ISearchComparison extends ISearchOperator {
public const COMPARE_LESS_THAN_EQUAL = 'lte';
public const COMPARE_LIKE = 'like';
public const COMPARE_LIKE_CASE_SENSITIVE = 'clike';
public const COMPARE_DEFINED = 'is-defined';

public const HINT_PATH_EQ_HASH = 'path_eq_hash'; // transform `path = "$path"` into `path_hash = md5("$path")`, on by default

Expand Down

0 comments on commit ee787cd

Please sign in to comment.