Repository navigation
Read a materialized top-K as a step in a multi-hop query (Step 6 of #386) - #498
Merged
Merged
Conversation
This was referenced Aug 20, 2026
zipdoki
force-pushed
the
feat/multihop-query
branch
from
August 27, 2026 04:53
168b835 to
1d90945
Compare
zipdoki
force-pushed
the
feat/multihop-query
branch
from
August 27, 2026 08:08
564d0aa to
5f72cc5
Compare
zipdoki
force-pushed
the
feat/multihop-query
branch
from
August 31, 2026 00:50
5f72cc5 to
cb9931e
Compare
Contributor
Author
|
Optimistic Merge |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A ranking gets written and can be read by naming its entity, but only through its own endpoint. Nothing can use one mid-query. Taking the accounts a user follows and then reading each one's top purchases needs two round trips and a client-side join. This adds a
TOPKstep to the multi-hop query so a ranking is just another hop.The step names the source table and the declaration on it; the rows come from the ranking table that declaration points at.
RankScan.from(...)resolves that table, itsmetricindex and the start key from(topk, entity, dimensionValues).entityis a vertex like any other hop's source, so it can be aREFinto an earlier step'sfield. Omit it and the step reads a global ranking.
{ "fetch": [ { "type": "SCAN", "name": "a", "database": "graph", "table": "follows", "index": "created_at", "source": { "type": "VALUE", "value": "user1" } }, { "type": "TOPK", "name": "b", "database": "graph", "table": "orders", "topk": "top_product_groups_1y", "entity": { "type": "REF", "ref": "a", "field": "tgt" }, "limit": 10, "include": true } ] }I renamed
querytofetch. There is no alias, so a payload written againstqueryfails to parse. Nothing is released on this path, so I took the break rather than carrying two names; if someone turns out to be on the old one, the alias is a one-liner.An empty result now carries the table's columns.
TableBinding.emptyFramereplacesDataFrame.emptyon the SCAN, CACHE and TOPK paths. A read that found nothing still came from a table that has columns, and dropping them turns "no rows" into "no such column" for anything that resolves names against the frame — the SQL transform stacked ontop of this cannot plan against a schema-less one. For a TOPK step the empty frame comes from the ranking table, not from the source table the step names, which costs one extra binding lookup on the empty path.
One thing this does not do: a TOPK step over several entities concatenates their rankings rather than merging them by metric, so the rows come back grouped per entity and not globally ordered. Ordering across entities is what the SQL transform in the next PR is for, and I'd rather do it there than hard-code a merge here.
Stacked on #497 — that needs to land first.
Part of #386.
Test plan
./gradlew :engine:test --tests '*TopkQuerySpec*'— per-entity, global and dimension-split rankings as a hop,REFentities, empty rankings./gradlew :engine:test --tests '*ActionbaseQueryParserSpec*'—TOPKstep parsing and thefetchkey./gradlew :engine:test --tests '*MultiHopQuerySpec*'— existing hops still chain./gradlew :server:test --tests '*ActionbaseQueryE2ETest*'— the query endpoint over HTTP./gradlew spotlessCheck build— formatting and full buildAI Assistance