Skip to content

fix(db): avoid passing computed orderBy aliases to loadSubset#1520

Open
andrewcoelho wants to merge 1 commit intoTanStack:mainfrom
andrewcoelho:main
Open

fix(db): avoid passing computed orderBy aliases to loadSubset#1520
andrewcoelho wants to merge 1 commit intoTanStack:mainfrom
andrewcoelho:main

Conversation

@andrewcoelho
Copy link
Copy Markdown

@andrewcoelho andrewcoelho commented May 7, 2026

🎯 Changes

TLDR: Fixes incorrect loadSubset optimization hints when a subquery orders by a computed selected field e.g. sortKey: coalesce(...).

I ran into this when trying to use an orderBy field that is a projected/computed alias. That was causing subset loading to use an invalid ordering hint, so the top-N window is built from the wrong subset and I observed query results that looked like limit was being applied before orderBy.

This change makes ref resolution stop at computed select expressions (only pass-through ref projections are followed), and only forwards orderBy hints when each orderBy ref resolves to a real source field.

  • Updated followRef in packages/db/src/query/ir.ts to return undefined for computed selected fields.
  • Applied the same followRef fix in packages/db/src/query/compiler/index.ts to keep compiler behavior consistent.
  • Tightened computeSubscriptionOrderByHints in packages/db/src/query/live/utils.ts to validate each orderBy ref via followRef before passing orderBy/limit hints to loadSubset.
  • Added a regression test in packages/db/tests/query/load-subset-subquery.test.ts to ensure computed subquery orderBy aliases do not forward orderBy/limit hints.

ETA sample query that shows the issue:

const pagedQuery = useLiveQuery(
  (q) => {
    // 1) Aggregate child records per parent (derived subquery)
    const latestChildByParent = q
      .from({ child: childCollection })
      .groupBy(({ child }) => child.parentId)
      .select(({ child }) => ({
        parentId: child.parentId,
        latestAt: max(child.createdAt),
      }))

    // 2) Join aggregate back to parent and compute a projected sort alias
    const parentsWithComputedSort = q
      .from({ parent: parentCollection })
      .join(
        { latest: latestChildByParent },
        ({ parent, latest }) => eq(parent.id, latest.parentId),
      )
      .select(({ parent, latest }) => ({
        id: parent.id,
        // computed/projection field (not a raw source column)
        computedSortKey: coalesce(latest.latestAt, parent.createdAt),
      }))

    // 3) Filter + order by computed alias + paginate
    return q
      .from({ row: parentsWithComputedSort })
      .where(({ row }) => gt(row.computedSortKey, anchorDate))
      .orderBy(({ row }) => row.computedSortKey, 'asc')
      .select(({ row }) => ({
        id: row.id,
        computedSortKey: row.computedSortKey,
      }))
      .limit(3)
  },
)

// Expected (ascending by computedSortKey):
// A
// B
// C

// Actual:
// A
// D
// E

// If I increase pageSize to include all rows:
// A
// B
// C
// D
// E
// ...

✅ Checklist

  • I have tested this code locally with pnpm test.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

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.

1 participant