diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index 3b1c48306a678..338e66ea7c5a9 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -1353,4 +1353,21 @@ public function quoteAlias($alias) { return $this->helper->quoteColumnName($alias); } + + /** + * Either appends to or replaces a single, generic query part. + * + * The available parts are: 'select', 'from', 'set', 'where', + * 'groupBy', 'having' and 'orderBy'. + * + * @param string $sqlPartName + * @param mixed $sqlPart + * @param bool $append + * + * @return $this This QueryBuilder instance. + */ + public function add(string $sqlPartName, $sqlPart, bool $append = false) { + $this->queryBuilder->add($sqlPartName, $sqlPart, $append); + return $this; + } } diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index 774691ebc3189..f10080768d011 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -25,6 +25,7 @@ */ namespace OC\Files\Cache; +use Doctrine\DBAL\Platforms\MySQLPlatform; use OC\DB\QueryBuilder\QueryBuilder; use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -41,12 +42,24 @@ public function __construct(IDBConnection $connection, SystemConfig $systemConfi parent::__construct($connection, $systemConfig, $logger); } - public function selectFileCache(string $alias = null) { + public function selectFileCache(string $alias = null, string $mysqlIndexHint = '') { $name = $alias ? $alias : 'filecache'; $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time') - ->from('filecache', $name) - ->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); + ->from('filecache', $name); + if ($mysqlIndexHint !== '' && $this->getConnection()->getDatabasePlatform() instanceof MySQLPlatform) { + $this->add('join', [ + $this->quoteAlias($name) => [ + // horrible query builder crimes to sneak in raw sql after the "FROM oc_filecache $name" + 'joinType' => $mysqlIndexHint . ' left', + 'joinTable' => $this->getTableName('filecache_extended'), + 'joinAlias' => $this->quoteAlias('fe'), + 'joinCondition' => $this->expr()->eq("$name.fileid", 'fe.fileid'), + ], + ], true); + } else { + $this->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); + } $this->alias = $name; diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index e065e95e4fd22..c3e6d91b28206 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -103,7 +103,13 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array $builder = $this->getQueryBuilder(); - $query = $builder->selectFileCache('file'); + // mysql really likes to pick an index for sorting if it can't fully satisfy the where + // filter with an index, since search queries pretty much never are fully filtered by index + // mysql often picks an index for sorting instead of the *much* more useful index for filtering. + // + // To bypass this, we tell mysql explicitly not to use the mtime (the default order field) index, + // so it will instead pick an index that is actually useful. + $query = $builder->selectFileCache('file', 'ignore index for order by (fs_mtime)'); if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) { $user = $searchQuery->getUser();