Skip to content

Performer scene pagination for pairings and studios - #1202

Merged
InfiniteStash merged 4 commits into
stashapp:masterfrom
Maista6969:performer-scene-pagination
Aug 26, 2026
Merged

Performer scene pagination for pairings and studios#1202
InfiniteStash merged 4 commits into
stashapp:masterfrom
Maista6969:performer-scene-pagination

Conversation

@Maista6969

Copy link
Copy Markdown
Contributor

I recently created a userscript that makes it possible to see all scene pairings for two performers instead of just the 10 most recent ones, but pretty soon I realized that this was probably something I should upstream into stash-box itself instead. I also realized that the same limitation appears in the Performers tab when viewing a single studio.

The problem

Performer.scenes builds its filter with the page size hardcoded to 10:

filter := models.SceneQueryInput{
    // ...
    Page:    1,
    PerPage: 10,
}

If two performers have done 200 scenes together, the page looks like they've done ten

What changed in the schema

I went with the extra argument to Performer.scenes rather than changing scenes to return a QueryScenesResultType like queryScenes does, mostly in an attempt to not break any consumers I don't know about: the extra argument is optional so it should behave exactly as before for anyone who may rely on it.

Happy to change that if necessary!

Decisions that could benefit from some input

I capped the per_page count to 100. The field is resolved once per performer in a queryPerformers result, so it'd multiply with the outer count which is capped to 1000. This may be overly cautious since we already allow fetching 1000 per page, but it'd be easy to drop.

Noticed that Infinite added some new data loaders to avoid N+1 queries in #1199 so I pre-emptively added one here that keys off pairs of IDs so it can be reused for the studio/performer pairings as well as performer/performer pairings.

The batch queries count every combination of the two ID sets rather than just the exact pairs asked for, and the caller picks out the ones it wants. That looks wasteful written down, but passing exact pairs meant feeding them through UNNEST(...) WITH ORDINALITY inside a CTE, and at that point sqlc can't tell they're uuid columns any more and types the whole result as interface{}. Selecting straight off scene_performers keeps the db_type: uuid override in sqlc.yaml working. In practice one side of the batch is a single id, so the combinations and the pairs are the same set.

I added a new sort value for SHARED_SCENE_COUNT rather than make SCENE_COUNT mean something different when performed_with is set: I think sorting by how many scenes performers actually have together is more helpful than the existing sort which is keyed off how many total scenes the other performers have. My last commit also makes this the default sort for the pairings tab, but that can easily be dropped if my assumption is too ambitious 😁

Testing

I've had the bot write some tests for this and ran several tests against my local dev instance, but I obviously couldn't point it at a live stash-box since it involves backend changes.

AI Disclosure

I had Claude Opus look over the plan and assist with writing tests for this PR

@InfiniteStash

Copy link
Copy Markdown
Collaborator

Thanks for the PR, definitely a useful addition.

I've gone a bit back and forth on the implementation. I think not changing the scenes return type makes sense, but I don't like the idea of having to pass synced filter params to both count and scenes. My preference would be to instead add a queryScenes field to Performer which returns QueryScenesResultType. Then you can just convert the PerformerScenesInput to SceneQueryInput and you're done basically. I guess we could also make it accept SceneQueryInput instead and just add a fixed INCLUDES for the performer. Then it could be used for other filters as well.

The dataloaders are nice to have, but I'm not sure they're particularly necessary. If it proves to be problematic from traces we can look into adding them back, along with batched scene data loading.

@Maista6969
Maista6969 force-pushed the performer-scene-pagination branch from 120d869 to 489fd99 Compare August 22, 2026 22:21
@Maista6969

Maista6969 commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, I guess I was a little too conservative and it ended up adding a lot of bloat 😅

I've added the new queryScenes field and had it take SceneQueryInput, removing my extra filter params and the dataloaders which cut down ~400 lines of code.

I tried the INCLUDES approach first, but I realized that this is only correct in some cases: if the callers filter had "includes performer A" then we would turn it into INCLUDES_ALL with [A, self] and that's fine, if the callers filter had "includes performer A or performer B" then that turns into INCLUDES_ALL with [A, B, self] which is more constrained, and if the callers filter had "excludes performer C" we'd turn that into INCLUDES_ALL with [C, self] which is the opposite of what they wanted. Even keeping the criterion modifier the caller used would turn that into EXCLUDES with [C, self] so we'd lose the performer scoping altogether and return wildly misleading results.

My suggested solution here is adding PerformerID *uuid.UUID to the SceneQuery struct so it can be carried all the way down through the resolver to the scene service where it is applied as its own MultiIDCriterion and this coexists with whatever the caller passed. We never override or merge what the caller wants, so hopefully that's less code for bugs to hide in :)

@InfiniteStash
InfiniteStash merged commit 2b32b34 into stashapp:master Aug 26, 2026
5 checks passed
@Maista6969
Maista6969 deleted the performer-scene-pagination branch August 26, 2026 08:58
Maista6969 added a commit to Maista6969/stash-box-toolkit that referenced this pull request Aug 27, 2026
stashapp/stash-box#1202 was merged so this userscript is no longer useful
on versions 0.10.4 and above
Maista6969 added a commit to Maista6969/stash-box-toolkit that referenced this pull request Aug 27, 2026
stashapp/stash-box#1202 was merged so this userscript is no longer useful
on versions 0.10.4 and above
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