Skip to content

fix(orm): fixed orderBy in nested queries (includes/selects)#2518

Open
eredzik wants to merge 4 commits intozenstackhq:devfrom
eredzik:fix/order-by-in-nested-queries
Open

fix(orm): fixed orderBy in nested queries (includes/selects)#2518
eredzik wants to merge 4 commits intozenstackhq:devfrom
eredzik:fix/order-by-in-nested-queries

Conversation

@eredzik
Copy link
Copy Markdown

@eredzik eredzik commented Mar 25, 2026

Summary

Fixes #2464

  • Fix ordering of to-many relation arrays when the relation query includes nested include/select clauses. The buildRelationOrderByExpressions method in the lateral join dialect was not propagating ORDER BY into the jsonb_agg / JSON_ARRAYAGG call, causing databases (especially PostgreSQL) to return relation arrays in arbitrary order when nested joins were present.

Test plan

  • New E2E test order-by-nested-includes.test.ts covers both SQLite and PostgreSQL:
    keeps stable order for to-many include with nested includes — creates 10 (SQLite) / 2000 (PostgreSQL) posts with descending insert order, queries with orderBy: { sequence: 'asc' } and 'desc' while including nested author and comments.author relations. Asserts the returned sequence arrays match the expected sorted order.
    keeps stable order for to-many select with nested selects — same scenario using select instead of include, verifying ordering is preserved through nested select clauses as well.
  • Existing ORM client-api and relation tests continue to pass (no behavioral change to the query logic, only type-level cleanup).

Summary by CodeRabbit

  • New Features

    • To-many relation results now respect requested ordering when included or selected, preserving sort direction across providers.
  • Compatibility

    • PostgreSQL honor ordering inside aggregated relation arrays; MySQL uses provider-specific deterministic ordering behavior.
  • Tests

    • Added end-to-end tests validating stable ordering for nested includes and nested selects.

…y aggregation

- Updated `buildArrayAgg` method to accept an optional `orderBy` parameter for sorting.
- Introduced `buildRelationOrderByExpressions` to handle orderBy logic for relations.
- Added tests for stable ordering in nested includes and selects for both SQLite and PostgreSQL.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This PR threads relation-level ordering into lateral-join aggregation: it normalizes relation orderBy expressions in the lateral join dialect, passes them into array-aggregation, implements ORDER BY inside PostgreSQL's jsonb_agg(...), adapts MySQL dialect signature (no-op for ordering), and adds e2e tests verifying nested include/select ordering.

Changes

Cohort / File(s) Summary
Lateral Join Dialect
packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts
Added buildRelationOrderByExpressions(...) to normalize payload.orderBy into { expr, sort, nulls? }[], skip falsy/relation fields, and return undefined when none; pass derived orderBy into buildArrayAgg(this.buildJsonObject(...), orderBy) for to-many relations; extended abstract buildArrayAgg contract.
PostgreSQL Dialect
packages/orm/src/client/crud/dialects/postgresql.ts
Changed buildArrayAgg(arg, orderBy?) to emit jsonb_agg(... ORDER BY ...) when orderBy is provided, including ASC/DESC and optional `NULLS FIRST
MySQL Dialect
packages/orm/src/client/crud/dialects/mysql.ts
Adjusted buildArrayAgg(arg, _orderBy?) signature to accept ordering metadata; implementation is a no-op regarding SQL generation (MySQL JSON_ARRAYAGG lacks ORDER BY), with a comment noting deterministic ordering is handled earlier.
End-to-End Tests
tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts
New Vitest e2e test file seeding Users/Posts/Comments and asserting stable ordering for to-many relations with nested includes and nested selects; tests both ascending and descending orderBy sequences and disconnect DB after each test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I dug through queries late at night,
Pushed ORDER BY into JSON's light,
Nested joins no longer stray,
Rows march home in proper array —
Crunching carrots, code feels right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main change: fixing orderBy support in nested queries with includes/selects.
Linked Issues check ✅ Passed All coding requirements from #2464 are met: ORDER BY is now propagated into jsonb_agg/JSON_ARRAYAGG, nested includes/selects preserve ordering, and E2E tests verify both PostgreSQL and select-based behaviors.
Out of Scope Changes check ✅ Passed All changes are in-scope: lateral join dialect base updated to compute orderBy, PostgreSQL and MySQL implementations updated to accept orderBy parameter, and E2E tests added to verify the fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts (1)

46-57: Consider adding orderBy test for nested comments.

The makeCommentsData function creates comments linked to posts, but the test doesn't verify ordering of the nested comments array within each post. While the PR focuses on fixing posts ordering when nested includes are present, adding a test case for multi-level ordering (e.g., comments: { orderBy: { content: 'asc' } }) could strengthen the regression coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts` around
lines 46 - 57, Add a new assertion or test case in the same spec that uses
makeCommentsData to verify ordering of the nested comments include: when
querying posts with include: { comments: { orderBy: { content: 'asc' } } } (or
similar nested include used in the existing test), ensure each returned post's
comments array is sorted by comment.content ascending; update or add
expectations in the test file
tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts referencing
the query where posts are fetched (the test that exercises nested includes) and
assert that for each post the comments list is ordered (e.g., compare sequence
of comment.content or ids) to cover the regression for multi-level ordering
while keeping makeCommentsData as the comment fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts`:
- Around line 189-217: The MySQL dialect's buildArrayAgg implementation must be
updated to match the abstract signature in LateralJoinDialectBase: change the
method signature of buildArrayAgg in
packages/orm/src/client/crud/dialects/mysql.ts to accept the additional optional
orderBy parameter (orderBy?: { expr: Expression<any>; sort: SortOrder; nulls?:
NullsOrder }[]), and modify the method body to incorporate that orderBy when
building the ARRAY_AGG/JSON_ARRAYAGG expression (use orderBy to produce an ORDER
BY clause passed into the aggregate expression if provided); keep the return
type AliasableExpression<any> and ensure any internal references to
buildArrayAgg or calls to it still compile with the new optional parameter.

---

Nitpick comments:
In `@tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts`:
- Around line 46-57: Add a new assertion or test case in the same spec that uses
makeCommentsData to verify ordering of the nested comments include: when
querying posts with include: { comments: { orderBy: { content: 'asc' } } } (or
similar nested include used in the existing test), ensure each returned post's
comments array is sorted by comment.content ascending; update or add
expectations in the test file
tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts referencing
the query where posts are fetched (the test that exercises nested includes) and
assert that for each post the comments list is ordered (e.g., compare sequence
of comment.content or ids) to cover the regression for multi-level ordering
while keeping makeCommentsData as the comment fixture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf40d0dc-2ece-4b23-a39a-3d0df92b3fae

📥 Commits

Reviewing files that changed from the base of the PR and between 826cc4e and 0a7b4c7.

📒 Files selected for processing (3)
  • packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts

Copy link
Copy Markdown
Member

@ymc9 ymc9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @eredzik , thanks for working on this! The changes related to postgres looks great to me. Please check my comments about mysql.

Copy link
Copy Markdown
Member

@ymc9 ymc9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @eredzik .

I believe mysql needs some more fixes since the JSON_ARRAYAGG doesn't preserve input order either. I'll make a separate change for that.

I've also simplified the tests and removed the multi-provider matrix since the CI automatically do multiple runs for each provider. Will merge once CI passes and publish a patch release.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts (2)

64-71: Consider extracting common setup to reduce duplication.

Both tests repeat identical setup: creating the client, user, posts, and comments. This could be extracted into a beforeEach or a shared setup helper to reduce duplication while maintaining test isolation.

♻️ Optional refactor to extract common setup
 describe('Relation orderBy with nested includes', () => {
     let db: any;
+    const count = 2000;
+
+    async function seedDatabase() {
+        db = await createTestClient(schema);
+        await db.user.create({ data: { id: 'u1', email: 'u1@example.com' } });
+        await db.post.createMany({ data: makePostsData(count) });
+        await db.comment.createMany({ data: makeCommentsData(count) });
+    }
 
     afterEach(async () => {
         await db?.$disconnect();
     });
 
     it('keeps stable order for to-many include with nested includes', async () => {
-        const count = 2000;
-
-        db = await createTestClient(schema);
-
-        await db.user.create({ data: { id: 'u1', email: 'u1@example.com' } });
-        await db.post.createMany({ data: makePostsData(count) });
-        await db.comment.createMany({ data: makeCommentsData(count) });
+        await seedDatabase();
 
         const user = await db.user.findFirst({

Also applies to: 100-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts` around
lines 64 - 71, Extract the repeated test setup (creating the TestClient via
createTestClient(schema), creating user with id 'u1', and creating
posts/comments via makePostsData and makeCommentsData) into a shared setup
routine called from a beforeEach in the test file so each test begins with the
same isolated state; specifically move the db = await createTestClient(schema)
and the three create/createMany calls into a beforeEach block (or into a helper
function invoked from beforeEach) and update the two tests (the one using count
= 2000 and the other at lines ~100-107) to rely on that shared setup while
keeping their local variations (like different count values) as needed.

77-77: Consider adding a test case for multi-column orderBy.

The PostgreSQL dialect supports multi-column ordering in buildArrayAgg (see packages/orm/src/client/crud/dialects/postgresql.ts), but the current tests only exercise single-column orderBy: { sequence: 'asc' | 'desc' }. Adding a test with multi-column ordering (e.g., orderBy: [{ title: 'asc' }, { sequence: 'desc' }]) would improve coverage.

Also applies to: 90-90, 114-114, 132-132

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts` at line
77, Add new e2e test cases that exercise multi-column ordering where the current
tests only use a single-column orderBy (the places using orderBy: { sequence:
'asc' }). For each of those occurrences, add a variant that passes an array for
orderBy (for example orderBy: [{ title: 'asc' }, { sequence: 'desc' }]) and
assert the resulting nested include ordering matches the combined sort; update
the tests that reference the single-column orderBy occurrences so they include
both the original single-column check and the new multi-column check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts`:
- Around line 64-71: Extract the repeated test setup (creating the TestClient
via createTestClient(schema), creating user with id 'u1', and creating
posts/comments via makePostsData and makeCommentsData) into a shared setup
routine called from a beforeEach in the test file so each test begins with the
same isolated state; specifically move the db = await createTestClient(schema)
and the three create/createMany calls into a beforeEach block (or into a helper
function invoked from beforeEach) and update the two tests (the one using count
= 2000 and the other at lines ~100-107) to rely on that shared setup while
keeping their local variations (like different count values) as needed.
- Line 77: Add new e2e test cases that exercise multi-column ordering where the
current tests only use a single-column orderBy (the places using orderBy: {
sequence: 'asc' }). For each of those occurrences, add a variant that passes an
array for orderBy (for example orderBy: [{ title: 'asc' }, { sequence: 'desc'
}]) and assert the resulting nested include ordering matches the combined sort;
update the tests that reference the single-column orderBy occurrences so they
include both the original single-column check and the new multi-column check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db2a44f1-ca9d-4d36-8d70-fd346acc7667

📥 Commits

Reviewing files that changed from the base of the PR and between c339fe7 and 16c196a.

📒 Files selected for processing (1)
  • tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts

@eredzik
Copy link
Copy Markdown
Author

eredzik commented Mar 27, 2026

Thanks for cleaning tests up, I was not aware that this is set up in this way for CI.

As for MySQL, when I ran MySQL tests on my PC with db in docker, tests returned correct ordering in both asc and desc cases with current version. I haven't ever used MySQL though so my knowledge there is limited unfortunately

@ymc9
Copy link
Copy Markdown
Member

ymc9 commented Mar 27, 2026

Thanks for cleaning tests up, I was not aware that this is set up in this way for CI.

As for MySQL, when I ran MySQL tests on my PC with db in docker, tests returned correct ordering in both asc and desc cases with current version. I haven't ever used MySQL though so my knowledge there is limited unfortunately

Same here, tests pass consistently, however mysql documentation seems to imply the order is not guaranteed, so may be one day someone will see a mismatch 🤷🏻‍♂️. I think it's safer to sort again post-read.

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.

orderBy on nested includes not working

2 participants