implements search on null/notnull metadatatags/v28.0.0rc1
@@ -1 +1 @@ | |||
Subproject commit d7b9f6f5f0513adc3ed652eb84b1822fb5b53032 | |||
Subproject commit b3d52b32c65999204aefeb85548f95c76391d632 |
@@ -169,11 +169,11 @@ class CachedSubscription extends \Sabre\CalDAV\Calendar { | |||
/** | |||
* @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'); | |||
} | |||
@@ -39,6 +39,7 @@ use OCP\Files\Cache\ICacheEntry; | |||
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; | |||
@@ -367,22 +368,30 @@ class FileSearchBackend implements ISearchBackend { | |||
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: | |||
@@ -416,7 +425,11 @@ class FileSearchBackend implements ISearchBackend { | |||
} | |||
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: |
@@ -49,6 +49,37 @@ class CommentsContext implements \Behat\Behat\Context\Context { | |||
} | |||
} | |||
/** | |||
* 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(); | |||
@@ -175,7 +206,7 @@ class CommentsContext implements \Behat\Behat\Context\Context { | |||
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); | |||
} | |||
} | |||
@@ -238,7 +269,8 @@ class CommentsContext implements \Behat\Behat\Context\Context { | |||
* @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)) { |
@@ -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 = [ | |||
@@ -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>!$_'; |
@@ -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 | |||