Skip to content

Only request the total count when it is needed - #102

Merged
woswos merged 6 commits into
masterfrom
rl-lazy-graph-query-omit-total-count
Sep 7, 2026
Merged

woswos merged 6 commits into
masterfrom
rl-lazy-graph-query-omit-total-count

Conversation

@LLipter

@LLipter LLipter commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

LazyGraphQuery added meta.totalCount to every graph list query so len() could answer without fetching, so every caller paid for a count over the whole matching set even after explicitly leaving meta out of its fields.

The count is now fetched on demand, and the things that used to force it no longer do:

  • a limit that fits one page is complete after the first call, so it needs no total at all
  • truthiness answers from that page instead of falling through to len(), so result['key'] or [] costs nothing (15 call sites in migration and sensorbridges use that shape)
  • an index inside that page is returned from it, covering result['key'][0]
  • paging never needed a total, GraphQueryIterator stops on a short page

When a total is genuinely required, the query keeps the caller's field selection and adds meta to it. This matters: a resolver such as ListBodies gates loading the rows on the data field being selected, so a query asking for meta alone counts an empty slice and reports zero.

Verified against a real webstack, counting SELECT count(...) in the server log:

scenario master this branch
bounded first=1, no meta 2 0
unbounded, construct only 1 0
unbounded, or [] truthiness 1 0
unbounded, index [0] 1 0
unbounded, full iterate 2 0
unbounded, len() 1 1
meta selected 1 1

Only a real length or an explicit meta selection now costs a count.

The return type is unchanged, callers still get a LazyGraphQuery with FetchAll(), totalCount and the other list helpers. Behaviour on the ListBodies shapes used by the sensor link migration (or [], [0], len()) is identical to master. The v1 LazyQuery behind GetScenes is untouched.

LazyGraphQuery added meta.totalCount to the selection of every graph list
query so that len() could answer without fetching. On the server that field
is a count over the whole matching set, so every caller paid for a scan it
had usually not asked for, and a caller that deliberately left meta out had
no way to avoid it.

Two changes remove it. A query whose limit already fits in one page has
nothing to paginate, so it now skips the lazy wrapper and runs as a single
plain query with the fields the caller chose. An unbounded query still goes
through LazyGraphQuery, but the count is fetched on demand by len(),
indexing and totalCount rather than up front, so a caller that only iterates
never pays for one. Paging itself never needed the count, GraphQueryIterator
stops when a page comes back short.

Fetching the whole result to measure it instead was considered and rejected.
That would defeat the point of the class on exactly the large tables it
exists to protect.

Callers that select meta keep the previous behaviour. The v1 LazyQuery
behind GetScenes is untouched, it takes its total from the REST meta.
@LLipter
LLipter requested a review from woswos September 3, 2026 05:46
@LLipter
LLipter marked this pull request as draft September 3, 2026 06:58
Comment thread python/mujinwebstackclient/webstackgraphclientutils.py Outdated
Review feedback: skipping the wrapper for a single page changed the payload
from LazyGraphQuery to a plain list, so callers lost FetchAll(), totalCount
and the other helpers. That made the API harder to use for a saving that can
be had without it.

UseLazyGraphQuery is back to what it was. Instead, the call the constructor
already makes to learn the key name and type name now fetches the whole
window when the requested limit fits in one page, and keeps it. The result is
complete at that point, so len(), indexing and repr() answer from the items
and still need no count, while the return type is unchanged.

repr() on such a query is now the real list rather than the elided partial
form, since the items are known to be the whole result.
Removing the up-front count from every lazy graph query was too broad. An
unbounded query returns a LazyGraphQuery whose len() and truthiness need a
total, and deferring that to an on-demand query changed the behaviour of
callers that iterate such a result. The sensor link migration reads
ListBodies that way, and with the total missing it stopped finding the
attached sensors, which left sensorbridges.conf with an empty sensor list
and broke the simulator test suite downstream.

Narrow it to the case that is provably free. A limit that already fits in
one page is satisfied by the call the constructor already makes, so fetch
the whole window there and keep it. That query is complete, needs no total,
and asks for no count. Every other query asks for the count up front exactly
as before, so nothing that pages changes at all.

This also removes the on-demand count and the len(), indexing and repr()
overrides that existed only to cope with a missing total.
Limiting the saving to single page queries left the common case paying for a
count nobody asked for. Fetch it on demand instead, and remove the reasons a
caller ends up needing it.

Truthiness now answers from the page fetched in the constructor rather than
falling through to len(), so the "result or []" shape costs nothing. There
are fifteen of those in migration and sensorbridges alone. An index that
falls inside that page is returned from it for the same reason, which covers
the "result[0]" shape. Paging already needed no total, GraphQueryIterator
stops when a page comes back short.

When a total really is required, the query keeps the caller's field
selection and adds meta to it. That matters: a resolver such as ListBodies
gates loading the rows on the data field being selected, so a query asking
for meta alone counts an empty slice and reports zero. Asking for meta on
its own is what previously made an unbounded result look empty and broke the
sensor link migration.
@LLipter

LLipter commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Pipeline #2092933

@LLipter
LLipter marked this pull request as ready for review September 4, 2026 04:53
@woswos

woswos commented Sep 7, 2026

Copy link
Copy Markdown
Member

Pipeline #2096092

@woswos
woswos merged commit b4bb653 into master Sep 7, 2026
1 check passed
@woswos

woswos commented Sep 7, 2026

Copy link
Copy Markdown
Member

Thank you!

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.

2 participants