1
0

MetadataQuery.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\FilesMetadata;
  8. use OC\FilesMetadata\Model\FilesMetadata;
  9. use OC\FilesMetadata\Service\IndexRequestService;
  10. use OC\FilesMetadata\Service\MetadataRequestService;
  11. use OCP\DB\QueryBuilder\IQueryBuilder;
  12. use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
  13. use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
  14. use OCP\FilesMetadata\IFilesMetadataManager;
  15. use OCP\FilesMetadata\IMetadataQuery;
  16. use OCP\FilesMetadata\Model\IFilesMetadata;
  17. use OCP\FilesMetadata\Model\IMetadataValueWrapper;
  18. use Psr\Log\LoggerInterface;
  19. /**
  20. * @inheritDoc
  21. * @since 28.0.0
  22. */
  23. class MetadataQuery implements IMetadataQuery {
  24. private array $knownJoinedIndex = [];
  25. public function __construct(
  26. private IQueryBuilder $queryBuilder,
  27. private IFilesMetadata|IFilesMetadataManager $manager,
  28. private string $fileTableAlias = 'fc',
  29. private string $fileIdField = 'fileid',
  30. private string $alias = 'meta',
  31. private string $aliasIndexPrefix = 'meta_index',
  32. ) {
  33. if ($manager instanceof IFilesMetadata) {
  34. /**
  35. * Since 29, because knownMetadata is stored in lazy appconfig, it seems smarter
  36. * to not call getKnownMetadata() at the load of this class as it is only needed
  37. * in {@see getMetadataValueField}.
  38. *
  39. * FIXME: remove support for IFilesMetadata
  40. */
  41. $logger = \OCP\Server::get(LoggerInterface::class);
  42. $logger->debug('It is deprecated to use IFilesMetadata as second parameter when calling MetadataQuery::__construct()');
  43. }
  44. }
  45. /**
  46. * @inheritDoc
  47. * @see self::extractMetadata()
  48. * @since 28.0.0
  49. */
  50. public function retrieveMetadata(): void {
  51. $this->queryBuilder->selectAlias($this->alias . '.json', 'meta_json');
  52. $this->queryBuilder->selectAlias($this->alias . '.sync_token', 'meta_sync_token');
  53. $this->queryBuilder->leftJoin(
  54. $this->fileTableAlias, MetadataRequestService::TABLE_METADATA, $this->alias,
  55. $this->queryBuilder->expr()->eq($this->fileTableAlias . '.' . $this->fileIdField, $this->alias . '.file_id')
  56. );
  57. }
  58. /**
  59. * @param array $row result row
  60. *
  61. * @inheritDoc
  62. * @return IFilesMetadata metadata
  63. * @see self::retrieveMetadata()
  64. * @since 28.0.0
  65. */
  66. public function extractMetadata(array $row): IFilesMetadata {
  67. $fileId = (array_key_exists($this->fileIdField, $row)) ? $row[$this->fileIdField] : 0;
  68. $metadata = new FilesMetadata((int)$fileId);
  69. try {
  70. $metadata->importFromDatabase($row, $this->alias . '_');
  71. } catch (FilesMetadataNotFoundException) {
  72. // can be ignored as files' metadata are optional and might not exist in database
  73. }
  74. return $metadata;
  75. }
  76. /**
  77. * @param string $metadataKey metadata key
  78. * @param bool $enforce limit the request only to existing metadata
  79. *
  80. * @inheritDoc
  81. * @since 28.0.0
  82. */
  83. public function joinIndex(string $metadataKey, bool $enforce = false): string {
  84. if (array_key_exists($metadataKey, $this->knownJoinedIndex)) {
  85. return $this->knownJoinedIndex[$metadataKey];
  86. }
  87. $aliasIndex = $this->aliasIndexPrefix . '_' . count($this->knownJoinedIndex);
  88. $this->knownJoinedIndex[$metadataKey] = $aliasIndex;
  89. $expr = $this->queryBuilder->expr();
  90. $andX = $expr->andX($expr->eq($aliasIndex . '.file_id', $this->fileTableAlias . '.' . $this->fileIdField));
  91. $andX->add($expr->eq($this->getMetadataKeyField($metadataKey), $this->queryBuilder->createNamedParameter($metadataKey)));
  92. if ($enforce) {
  93. $this->queryBuilder->innerJoin(
  94. $this->fileTableAlias,
  95. IndexRequestService::TABLE_METADATA_INDEX,
  96. $aliasIndex,
  97. $andX
  98. );
  99. } else {
  100. $this->queryBuilder->leftJoin(
  101. $this->fileTableAlias,
  102. IndexRequestService::TABLE_METADATA_INDEX,
  103. $aliasIndex,
  104. $andX
  105. );
  106. }
  107. return $aliasIndex;
  108. }
  109. /**
  110. * @throws FilesMetadataNotFoundException
  111. */
  112. private function joinedTableAlias(string $metadataKey): string {
  113. if (!array_key_exists($metadataKey, $this->knownJoinedIndex)) {
  114. throw new FilesMetadataNotFoundException('table related to ' . $metadataKey . ' not initiated, you need to use leftJoin() first.');
  115. }
  116. return $this->knownJoinedIndex[$metadataKey];
  117. }
  118. /**
  119. * @inheritDoc
  120. *
  121. * @param string $metadataKey metadata key
  122. *
  123. * @return string table field
  124. * @throws FilesMetadataNotFoundException
  125. * @since 28.0.0
  126. */
  127. public function getMetadataKeyField(string $metadataKey): string {
  128. return $this->joinedTableAlias($metadataKey) . '.meta_key';
  129. }
  130. /**
  131. * @inheritDoc
  132. *
  133. * @param string $metadataKey metadata key
  134. *
  135. * @return string table field
  136. * @throws FilesMetadataNotFoundException if metadataKey is not known
  137. * @throws FilesMetadataTypeException is metadataKey is not set as indexed
  138. * @since 28.0.0
  139. */
  140. public function getMetadataValueField(string $metadataKey): string {
  141. if ($this->manager instanceof IFilesMetadataManager) {
  142. /**
  143. * Since 29, because knownMetadata is stored in lazy appconfig, it seems smarter
  144. * to not call getKnownMetadata() at the load of this class as it is only needed
  145. * in this method.
  146. *
  147. * FIXME: keep only this line and remove support for previous IFilesMetadata in constructor
  148. */
  149. $knownMetadata = $this->manager->getKnownMetadata();
  150. } else {
  151. $knownMetadata = $this->manager;
  152. }
  153. return match ($knownMetadata->getType($metadataKey)) {
  154. IMetadataValueWrapper::TYPE_STRING => $this->joinedTableAlias($metadataKey) . '.meta_value_string',
  155. IMetadataValueWrapper::TYPE_INT, IMetadataValueWrapper::TYPE_BOOL => $this->joinedTableAlias($metadataKey) . '.meta_value_int',
  156. default => throw new FilesMetadataTypeException('metadata is not set as indexed'),
  157. };
  158. }
  159. }