Skip to content

Add fileCount GraphQL query for match counts without paging — Closes #77#79

Draft
conradbzura wants to merge 3 commits into
masterfrom
77-add-filecount-query
Draft

Add fileCount GraphQL query for match counts without paging — Closes #77#79
conradbzura wants to merge 3 commits into
masterfrom
77-add-filecount-query

Conversation

@conradbzura

@conradbzura conradbzura commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add a fileCount GraphQL query that returns the number of files matching a filter without fetching any file documents. Obtaining a match count previously meant issuing the files query, which also fetches and hydrates a page of documents the caller then discards — wasteful for clients that only need the size of a result set (pagination controls, "N files match" labels, faceting UIs that count many filter permutations).

The new resolver mirrors the existing distinctValues query in form: it accepts the same [FileMetadataInput] filter shape as files/distinctValues, reuses the shared to_query(to_dict(input)) filter-building path and the wait_for_cutover guard, and returns a scalar Int! by running only count_documents against the filter. An absent (or empty) filter counts every file.

This branch is standalone on master, where files still returns a bare list; there is no totalCount envelope to reconcile (that work lives on a separate branch/PR).

Closes #77

Example

Count files from a specific DCC:

query {
  fileCount(input: [{ dcc: [{ dccAbbreviation: ["4DN"] }] }])
}

Count every file (no filter):

query {
  fileCount
}

Response:

{ "data": { "fileCount": 1234 } }

Over HTTP:

curl -X POST http://localhost:8000/metadata \
  -H "Content-Type: application/json" \
  -d '{"query": "{ fileCount(input: [{ dcc: [{ dccAbbreviation: [\"4DN\"] }] }]) }"}'

Proposed changes

Add the fileCount resolver

Add Query.file_count in src/cfdb/api/gql/schema.py, exposed in the schema as fileCount(input: [FileMetadataInput!] = null): Int!. It awaits locks.wait_for_cutover(), builds the Mongo filter with to_query(to_dict(input)) if input else {} (identical to files/distinct_values), and returns await api.db.files.count_documents(query). Regenerate the committed schema.graphql SDL snapshot to add the field.

Document the query

Note fileCount in the README GraphQL Queries section with an example, showing it takes the same FileMetadataInput filter as files and returns a count without fetching documents.

Cover the query with tests

Add TestFileCountQuery to tests/test_schema.py exercising the resolver end-to-end through schema.execute, including OR/AND filter construction, cross-query agreement with files, and a property-based invariant.

Test cases

# Test Suite Given When Then Coverage Target
1 TestFileCountQuery Three files spanning multiple DCCs fileCount runs with no input Returns the total (3) Empty-filter count-all
2 TestFileCountQuery Two HuBMAP files and one 4DN file fileCount runs with a DCC filter for HuBMAP Returns only the matches (2) Single-field filter honored
3 TestFileCountQuery Three files, none from the filtered DCC fileCount runs with a filter for an absent DCC Returns 0 Filter matching nothing
4 TestFileCountQuery An empty database fileCount runs with no input Returns 0 Empty database
5 TestFileCountQuery Three files fileCount runs with an empty input list Returns the total (3) Empty-list truthiness edge
6 TestFileCountQuery Files across HuBMAP, 4DN, and ENCODE fileCount runs with one field listing two abbreviations Returns the union of the two DCCs OR across list values
7 TestFileCountQuery Three files with distinct local IDs fileCount runs with two separate input entries Returns the union of the entries OR across input entries
8 TestFileCountQuery A public and a non-public HuBMAP file plus a public 4DN file fileCount runs with a DCC and a data-access-level filter Returns only the file matching both AND across fields
9 TestFileCountQuery A mixed multi-DCC dataset smaller than one page fileCount and files run with the same filter fileCount equals the files result length Cross-query agreement
10 TestFileCountQuery A generated set of files and a generated filter subset fileCount runs with that filter Equals the number of matching documents Count invariant over a wide domain

Obtaining the number of files matching a filter meant issuing the files
query, which also fetches and hydrates a page of file documents the
caller then discards. Clients that only need the size of a result set --
sizing pagination controls, "N files match" labels, faceting UIs that
count many filter permutations -- paid for a page they never use.

fileCount accepts the same FileMetadataInput filter shape as files and
distinctValues and returns a scalar Int, running only count_documents
against the filter. It reuses the shared to_query/to_dict filter path
and the cutover wait, so an absent filter counts every file.

Claude-Session: https://claude.ai/code/session_01Y5eYL9rN5xCDbwTQZrjwky
Note fileCount in the GraphQL Queries section with an example showing it
takes the same FileMetadataInput filter as files and returns a count
without fetching any documents.

Claude-Session: https://claude.ai/code/session_01Y5eYL9rN5xCDbwTQZrjwky
Add TestFileCountQuery: the unfiltered total, a filtered subset, no-match
and empty-database zeros, the empty-input-list count-all edge, OR across
multiple values in one field, OR across multiple input entries, AND
across multiple fields, agreement with the files result length for the
same filter, and a property-based invariant that the count equals the
number of documents matching a generated filter.

Claude-Session: https://claude.ai/code/session_01Y5eYL9rN5xCDbwTQZrjwky
@conradbzura conradbzura self-assigned this Jul 18, 2026
@conradbzura

Copy link
Copy Markdown
Collaborator Author

FYI @john-conroy - the PR body has usage examples for this. Decided to make it a separate endpoint since it's not a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add fileCount GraphQL query for match counts without paging

1 participant