You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MetadataQuery.php 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
  5. *
  6. * @author Maxence Lange <maxence@artificial-owl.com>
  7. *
  8. * @license GNU AGPL version 3 or any later version
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as
  12. * published by the Free Software Foundation, either version 3 of the
  13. * License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. */
  24. namespace OC\FilesMetadata;
  25. use OC\FilesMetadata\Model\FilesMetadata;
  26. use OC\FilesMetadata\Service\IndexRequestService;
  27. use OC\FilesMetadata\Service\MetadataRequestService;
  28. use OCP\DB\QueryBuilder\IQueryBuilder;
  29. use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
  30. use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
  31. use OCP\FilesMetadata\IFilesMetadataManager;
  32. use OCP\FilesMetadata\IMetadataQuery;
  33. use OCP\FilesMetadata\Model\IFilesMetadata;
  34. use OCP\FilesMetadata\Model\IMetadataValueWrapper;
  35. use Psr\Log\LoggerInterface;
  36. /**
  37. * @inheritDoc
  38. * @since 28.0.0
  39. */
  40. class MetadataQuery implements IMetadataQuery {
  41. private array $knownJoinedIndex = [];
  42. public function __construct(
  43. private IQueryBuilder $queryBuilder,
  44. private IFilesMetadata|IFilesMetadataManager $manager,
  45. private string $fileTableAlias = 'fc',
  46. private string $fileIdField = 'fileid',
  47. private string $alias = 'meta',
  48. private string $aliasIndexPrefix = 'meta_index'
  49. ) {
  50. if ($manager instanceof IFilesMetadata) {
  51. /**
  52. * Since 29, because knownMetadata is stored in lazy appconfig, it seems smarter
  53. * to not call getKnownMetadata() at the load of this class as it is only needed
  54. * in {@see getMetadataValueField}.
  55. *
  56. * FIXME: remove support for IFilesMetadata
  57. */
  58. $logger = \OCP\Server::get(LoggerInterface::class);
  59. $logger->debug('It is deprecated to use IFilesMetadata as second parameter when calling MetadataQuery::__construct()');
  60. }
  61. }
  62. /**
  63. * @inheritDoc
  64. * @see self::extractMetadata()
  65. * @since 28.0.0
  66. */
  67. public function retrieveMetadata(): void {
  68. $this->queryBuilder->selectAlias($this->alias . '.json', 'meta_json');
  69. $this->queryBuilder->selectAlias($this->alias . '.sync_token', 'meta_sync_token');
  70. $this->queryBuilder->leftJoin(
  71. $this->fileTableAlias, MetadataRequestService::TABLE_METADATA, $this->alias,
  72. $this->queryBuilder->expr()->eq($this->fileTableAlias . '.' . $this->fileIdField, $this->alias . '.file_id')
  73. );
  74. }
  75. /**
  76. * @param array $row result row
  77. *
  78. * @inheritDoc
  79. * @return IFilesMetadata metadata
  80. * @see self::retrieveMetadata()
  81. * @since 28.0.0
  82. */
  83. public function extractMetadata(array $row): IFilesMetadata {
  84. $fileId = (array_key_exists($this->fileIdField, $row)) ? $row[$this->fileIdField] : 0;
  85. $metadata = new FilesMetadata((int)$fileId);
  86. try {
  87. $metadata->importFromDatabase($row, $this->alias . '_');
  88. } catch (FilesMetadataNotFoundException) {
  89. // can be ignored as files' metadata are optional and might not exist in database
  90. }
  91. return $metadata;
  92. }
  93. /**
  94. * @param string $metadataKey metadata key
  95. * @param bool $enforce limit the request only to existing metadata
  96. *
  97. * @inheritDoc
  98. * @since 28.0.0
  99. */
  100. public function joinIndex(string $metadataKey, bool $enforce = false): string {
  101. if (array_key_exists($metadataKey, $this->knownJoinedIndex)) {
  102. return $this->knownJoinedIndex[$metadataKey];
  103. }
  104. $aliasIndex = $this->aliasIndexPrefix . '_' . count($this->knownJoinedIndex);
  105. $this->knownJoinedIndex[$metadataKey] = $aliasIndex;
  106. $expr = $this->queryBuilder->expr();
  107. $andX = $expr->andX($expr->eq($aliasIndex . '.file_id', $this->fileTableAlias . '.' . $this->fileIdField));
  108. $andX->add($expr->eq($this->getMetadataKeyField($metadataKey), $this->queryBuilder->createNamedParameter($metadataKey)));
  109. if ($enforce) {
  110. $this->queryBuilder->innerJoin(
  111. $this->fileTableAlias,
  112. IndexRequestService::TABLE_METADATA_INDEX,
  113. $aliasIndex,
  114. $andX
  115. );
  116. } else {
  117. $this->queryBuilder->leftJoin(
  118. $this->fileTableAlias,
  119. IndexRequestService::TABLE_METADATA_INDEX,
  120. $aliasIndex,
  121. $andX
  122. );
  123. }
  124. return $aliasIndex;
  125. }
  126. /**
  127. * @throws FilesMetadataNotFoundException
  128. */
  129. private function joinedTableAlias(string $metadataKey): string {
  130. if (!array_key_exists($metadataKey, $this->knownJoinedIndex)) {
  131. throw new FilesMetadataNotFoundException('table related to ' . $metadataKey . ' not initiated, you need to use leftJoin() first.');
  132. }
  133. return $this->knownJoinedIndex[$metadataKey];
  134. }
  135. /**
  136. * @inheritDoc
  137. *
  138. * @param string $metadataKey metadata key
  139. *
  140. * @return string table field
  141. * @throws FilesMetadataNotFoundException
  142. * @since 28.0.0
  143. */
  144. public function getMetadataKeyField(string $metadataKey): string {
  145. return $this->joinedTableAlias($metadataKey) . '.meta_key';
  146. }
  147. /**
  148. * @inheritDoc
  149. *
  150. * @param string $metadataKey metadata key
  151. *
  152. * @return string table field
  153. * @throws FilesMetadataNotFoundException if metadataKey is not known
  154. * @throws FilesMetadataTypeException is metadataKey is not set as indexed
  155. * @since 28.0.0
  156. */
  157. public function getMetadataValueField(string $metadataKey): string {
  158. if ($this->manager instanceof IFilesMetadataManager) {
  159. /**
  160. * Since 29, because knownMetadata is stored in lazy appconfig, it seems smarter
  161. * to not call getKnownMetadata() at the load of this class as it is only needed
  162. * in this method.
  163. *
  164. * FIXME: keep only this line and remove support for previous IFilesMetadata in constructor
  165. */
  166. $knownMetadata = $this->manager->getKnownMetadata();
  167. } else {
  168. $knownMetadata = $this->manager;
  169. }
  170. return match ($knownMetadata->getType($metadataKey)) {
  171. IMetadataValueWrapper::TYPE_STRING => $this->joinedTableAlias($metadataKey) . '.meta_value_string',
  172. IMetadataValueWrapper::TYPE_INT, IMetadataValueWrapper::TYPE_BOOL => $this->joinedTableAlias($metadataKey) . '.meta_value_int',
  173. default => throw new FilesMetadataTypeException('metadata is not set as indexed'),
  174. };
  175. }
  176. }