Overview only ever fetches the first 100 office files in total, so every category under-reports and some render empty
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 6
- Forks
- 1
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 10
Description
Summary
OfficeOverview fetches the files for every category in a single unpaginated DAV SEARCH, then splits the results into categories client-side. The request omits <d:limit>, so the server applies its default of 100 results.
The consequence is that the Overview can never display more than 100 office files in total, across all categories combined, no matter how large the library is:
- Every category under-reports once the library exceeds 100 office files.
- Because the 100-row budget is spent in
oc_mimetypes.idorder (see the query plan below), the categories whose mimetype IDs sort later can receive zero rows and render "No … found" while the files plainly exist.
There is no indication in the UI that anything was truncated.
Observed on a real instance
| Category | Files in the account | Shown in Overview |
|---|---|---|
| Documents | 293 (194 .doc + 99 .docx) |
100 (99 .docx + 1 .doc) |
| Presentations | 102 (45 .ppt + 57 .pptx) |
0 — "No Presentations found" |
All of these files are owned by the current user, live under the searched root, and have correct mimetypes in oc_filecache. So ~66% of Documents and 100% of Presentations are missing from the page.
Root cause
src/services/officeFiles.ts builds the search with no limit element:
return `<?xml version="1.0" encoding="UTF-8"?>
<d:searchrequest ${getDavNameSpaces()}>
<d:basicsearch>
<d:select>...</d:select>
<d:from>
<d:scope>
<d:href>${getRootPath()}/</d:href>
<d:depth>infinity</d:depth>
</d:scope>
</d:from>
<d:where>
<d:or>
${conditions}
</d:or>
</d:where>
</d:basicsearch>
</d:searchrequest>`
and getAllOfficeFiles() is called once with the union of every creator's mimetypes, caching the result for all categories.
When no limit is supplied, the server defaults to 100 — apps/dav/lib/Files/FileSearchBackend.php:
$limit = $query->limit;
$maxResults = $limit->maxResults !== 0 ? (int)$limit->maxResults : 100;
Why the loss is deterministic, not random
No ORDER BY is applied, so row order comes from the query plan — and the plan groups rows by mimetype ID ascending. EXPLAIN for the query shape the backend issues (storage + path prefix + mimetype IN (…), LIMIT 100):
1 SIMPLE s const PRIMARY,storages_id_index storages_id_index 259 const 1 Using index
1 SIMPLE m range PRIMARY,mimetype_id_index mimetype_id_index 1022 NULL 6 Using where; Using index
1 SIMPLE f ref …,fs_storage_mimetype,… fs_storage_mimetype 16 const,…m.id 178 Using where
oc_mimetypes is range-scanned on mimetype_id_index (so mimetypes are visited in ID order), and oc_filecache is joined per mimetype via fs_storage_mimetype on (storage, mimetype). The scan therefore emits all files of the lowest mimetype ID, then the next, until the limit is hit.
With the IDs in this instance:
| mimetype id | mimetype | files | rows consumed of the 100 |
|---|---|---|---|
| 5 | …wordprocessingml.document |
99 | 99 |
| 29 | application/msword |
194 | 1 |
| 32 | application/vnd.ms-powerpoint |
45 | 0 — never reached |
| 33 | …presentationml.presentation |
57 | 0 — never reached |
Replicating the query with ORDER BY mimetype LIMIT 100 returns exactly 99 .docx + 1 .doc, matching the UI exactly. (Ordering the same set by fileid instead would have returned 16 presentations — so this is specifically an artefact of the mimetype-ordered plan, and which categories starve depends on the account's mimetype ID distribution.)
Filtering happens after this truncated fetch, so All / Mine / Shared with me cannot recover the missing files.
Steps to reproduce
- Have more than 100 office files in the account, where a mimetype with a low
oc_mimetypes.idaccounts for ≈100 or more rows. - Open Office → Overview.
- Any category: only a subset of the files appears.
- A category whose mimetype ID sorts after the first 100 rows shows "No … found" despite the files existing.
Expected behaviour
Every category lists its files regardless of total library size — or at minimum, the UI indicates that results were truncated.
Notes / possible directions
- The existing
TODOinofficeFiles.tsanticipates the payload cost of an unpaginated search, but assumes it returns the full result set. It doesn't: the server silently truncates at 100. - Per-category searches (one SEARCH per creator's mimetypes, issued when the category is opened) would bound the payload and stop one category starving the others. This looks like the smallest correct fix.
- Adding an explicit
<d:limit>to the current single search only raises the ceiling; it does not remove the starvation, since one mimetype can still consume the whole budget. MAX_DISPLAY_FILES = 200exceeds the server's effective default of 100, so the UI can never reach its own display limit — which may be why the cap went unnoticed.- Possibly related, possibly intended: with
richdocumentsdoc_format=ooxml, the searched union contains only OOXML and legacy MS mimetypes, so.odt/.ods/.odpfiles never appear in the Overview at all.
Versions
| Component | Version |
|---|---|
| office | 1.0.0 |
| Nextcloud | 34.0.2 |
| richdocuments | 11.1.0 (doc_format=ooxml) |
| Database | MariaDB 11 |
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/services/officeFiles.ts, especially the search construction, getAllOfficeFiles(), the existing TODO, and MAX_DISPLAY_FILES. Read apps/dav/lib/Files/FileSearchBackend.php to confirm the default limit, then reproduce the issue with more than 100 office files and inspect how categories consume the cached union. Done means every category lists its files without one mimetype starving later categories, or truncation is clearly indicated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, typescript
- Domain
- backend, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 57/100