Skip to content

fix(orm): fix _count returning 0 for self-referential relations on delegate models#2555

Merged
ymc9 merged 3 commits intodevfrom
fix/delegate-count-self-referential
Apr 4, 2026
Merged

fix(orm): fix _count returning 0 for self-referential relations on delegate models#2555
ymc9 merged 3 commits intodevfrom
fix/delegate-count-self-referential

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented Apr 3, 2026

Summary

  • _count aggregation always returned 0 for self-referential relations on @@delegate models (e.g. Post.replies: Post[] where Post extends Content with @@delegate)
  • Root cause: buildCountJson used fieldModel as the subquery alias; for self-referential relations fieldModel === model, so both sides of the correlated WHERE clause resolved to the inner table — no rows matched
  • Fix: generate a unique subQueryAlias via tmpAlias() for the subquery, so the correlated join correctly references the outer vs inner table (same pattern used by buildToOneRelationFilter and buildRelationJoinFilter)

Fixes #2452

Test plan

  • Added regression test tests/regression/test/issue-2452.test.ts that reproduces the bug (creates a parent Post with 2 replies, asserts _count.replies === 2)
  • All 160 regression tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Corrected relation count queries that could report wrong counts for self-referential and delegated relations, ensuring accurate _count results when including related records.
  • Tests

    • Added a regression test that creates a self-referential scenario, inserts nested replies, and verifies _count returns the expected value.

…legate models

When buildCountJson builds a correlated subquery for _count, it was using
fieldModel as the subquery alias. For self-referential relations on delegate
models (where fieldModel === model), both sides of the correlated WHERE clause
resolved to the inner table, so no rows matched and count was always 0.

Fix by generating a unique subQueryAlias via tmpAlias() for the subquery,
matching the pattern already used by buildToOneRelationFilter and
buildRelationJoinFilter.

Fixes #2452

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 3, 2026

Claude finished @ymc9's task —— View job


Code Review Complete ✅

I've analyzed this PR thoroughly. Here's my comprehensive review:

Todo List

  • Read and analyze the main fix in base-dialect.ts
  • Review the regression test implementation
  • Check for code quality and best practices
  • Assess potential bugs or edge cases
  • Evaluate performance considerations
  • Check for security concerns
  • Review test coverage adequacy
  • Provide comprehensive feedback

Review Summary

This is a well-executed bug fix that addresses a specific edge case with _count aggregation in self-referential relations on delegate models. The fix is surgical, well-reasoned, and follows existing patterns in the codebase.

Strengths

1. Correct Root Cause Analysis

  • The issue description accurately identifies the problem: fieldModel === model causing both sides of the correlated WHERE clause to resolve to the same table
  • The fix directly addresses this by generating a unique alias via tmpAlias()

2. Consistent with Existing Patterns

  • Uses the same pattern already established in buildToOneRelationFilter (lines 328) and buildRelationJoinFilter methods
  • Follows the codebase convention of using tmpAlias() for subquery disambiguation

3. Comprehensive Test Coverage

  • The regression test in issue-2452.test.ts perfectly reproduces the bug scenario
  • Tests the exact case mentioned in the issue: delegate model with self-referential relation
  • Validates the fix works correctly (expects 2 replies, not 0)

4. Clean Implementation

  • Minimal, focused changes that don't affect other functionality
  • Good code comments explaining the reasoning
  • No breaking changes to existing APIs

🔧 Technical Analysis

Change Breakdown:

// Before: Using fieldModel directly as alias
fieldCountQuery = this.buildModelSelect(fieldModel, fieldModel, value as any, false)

// After: Using unique subquery alias
const subQueryAlias = tmpAlias(`${parentAlias}$_${field}$count`);
fieldCountQuery = this.buildModelSelect(fieldModel, subQueryAlias, value as any, false)

The fix correctly handles both many-to-many and one-to-many relations by:

  1. Generating a unique alias for the subquery (packages/orm/src/client/crud/dialects/base-dialect.ts:1297)
  2. Using this alias consistently in buildModelSelect, join references, and buildJoinPairs calls
  3. Maintaining proper correlation between outer and inner query tables

🎯 Quality Assessments

Performance:No Impact

  • Change only affects alias generation - no additional queries or complexity
  • tmpAlias() is a lightweight string generation utility

Security:No Concerns

  • No user input processing or SQL injection vectors
  • Internal aliasing mechanism with controlled inputs

Backward Compatibility:Preserved

  • No changes to public APIs or behavior for existing working cases
  • Only fixes broken edge case behavior

Edge Cases:Well Handled

  • Fix specifically targets the problematic case (self-referential + delegate models)
  • Regular relations unaffected
  • Many-to-many relations handled correctly

📋 Minor Observations

Code Style: The code follows established patterns and formatting. The comment on line 1295-1296 could be slightly more concise, but it's clear and helpful.

Test Quality: The regression test is excellent - it creates the exact scenario (delegate model + self-referential relation) and verifies the fix works. Consider adding a test case for many-to-many self-referential relations as well, though the current fix should handle both.

Recommendation

APPROVE - This is a high-quality bug fix that:

The implementation is solid and ready for merge.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b9c861b5-d51e-4342-a5fe-48b438dea1d7

📥 Commits

Reviewing files that changed from the base of the PR and between f448413 and 3a475ce.

📒 Files selected for processing (1)
  • tests/regression/test/issue-2452.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/regression/test/issue-2452.test.ts

📝 Walkthrough

Walkthrough

Introduces a distinct sub-query alias in the count JSON builder to ensure relation count subqueries use unique aliases, fixing incorrect _count results for self-referential relations in delegated models.

Changes

Cohort / File(s) Summary
Dialect Base Fix
packages/orm/src/client/crud/dialects/base-dialect.ts
Added subQueryAlias and used it when building relation-count subqueries and join pairs to avoid alias reuse in self-referential delegate scenarios (many-to-many and non-many-to-many cases).
Regression Test
tests/regression/test/issue-2452.test.ts
Added a Vitest regression test that creates a delegated self-referential Post with two replies and asserts _count.select.replies returns 2.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I hopped through aliases, small and spry,
Found a clash where counts ran shy,
I planted a subQuery name so neat,
Now replies are counted — two, complete! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: fixing _count aggregation for self-referential relations on delegate models.
Linked Issues check ✅ Passed The code changes directly address the root cause identified in issue #2452 by introducing a unique subQueryAlias to prevent ambiguous alias references in self-referential delegate model counts.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the _count issue for self-referential relations on delegate models: modifications to buildCountJson logic and a regression test.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/delegate-count-self-referential

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.

…tibility

Postgres enums are case-sensitive; enum values must match the model name
used as the delegate discriminator (e.g. 'Post' not 'POST').

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 4, 2026

Claude finished @ymc9's task —— View job


Code Review for PR #2555

I've analyzed this PR thoroughly. Here's my comprehensive review:

Todo List

  • Read and analyze the main fix in base-dialect.ts
  • Review the regression test implementation
  • Check for code quality and best practices
  • Assess potential bugs or edge cases
  • Evaluate performance considerations
  • Check for security concerns
  • Review test coverage adequacy
  • Provide comprehensive feedback

Review Summary

This is a well-executed bug fix that addresses a specific edge case with _count aggregation in self-referential relations on delegate models. The fix is surgical, well-reasoned, and follows existing patterns in the codebase.

Strengths

1. Correct Root Cause Analysis

  • The issue description accurately identifies the problem: fieldModel === model causing both sides of the correlated WHERE clause to resolve to the same table
  • The fix directly addresses this by generating a unique alias via tmpAlias()

2. Consistent with Existing Patterns

  • Uses the same pattern already established in buildToOneRelationFilter (lines 328) and buildRelationJoinFilter methods
  • Follows the codebase convention of using tmpAlias() for subquery disambiguation

3. Comprehensive Test Coverage

  • The regression test in issue-2452.test.ts perfectly reproduces the bug scenario
  • Tests the exact case mentioned in the issue: delegate model with self-referential relation
  • Validates the fix works correctly (expects 2 replies, not 0)

4. Clean Implementation

  • Minimal, focused changes that don't affect other functionality
  • Good code comments explaining the reasoning
  • No breaking changes to existing APIs

🔧 Technical Analysis

Change Breakdown:

// Before: Using fieldModel directly as alias
fieldCountQuery = this.buildModelSelect(fieldModel, fieldModel, value as any, false)

// After: Using unique subquery alias
const subQueryAlias = tmpAlias(`${parentAlias}$_${field}$count`);
fieldCountQuery = this.buildModelSelect(fieldModel, subQueryAlias, value as any, false)

The fix correctly handles both many-to-many and one-to-many relations by:

  1. Generating a unique alias for the subquery (packages/orm/src/client/crud/dialects/base-dialect.ts:1297)
  2. Using this alias consistently in buildModelSelect, join references, and buildJoinPairs calls
  3. Maintaining proper correlation between outer and inner query tables

🎯 Quality Assessments

Performance:No Impact

  • Change only affects alias generation - no additional queries or complexity
  • tmpAlias() is a lightweight string generation utility

Security:No Concerns

  • No user input processing or SQL injection vectors
  • Internal aliasing mechanism with controlled inputs

Backward Compatibility:Preserved

  • No changes to public APIs or behavior for existing working cases
  • Only fixes broken edge case behavior

Edge Cases:Well Handled

  • Fix specifically targets the problematic case (self-referential + delegate models)
  • Regular relations unaffected
  • Many-to-many relations handled correctly

📋 Minor Observations

Code Style: The code follows established patterns and formatting. The comment on line 1295-1296 could be slightly more concise, but it's clear and helpful.

Test Quality: The regression test is excellent - it creates the exact scenario (delegate model + self-referential relation) and verifies the fix works. Consider adding a test case for many-to-many self-referential relations as well, though the current fix should handle both.

Recommendation

APPROVE - This is a high-quality bug fix that:

The implementation is solid and ready for merge.


@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 4, 2026

Claude finished @ymc9's task —— View job


Code Review for PR #2555

I've completed my comprehensive review of this pull request that fixes _count returning 0 for self-referential relations on delegate models.

Todo List

  • Read and analyze the main fix in base-dialect.ts
  • Review the regression test implementation
  • Check for code quality and best practices
  • Assess potential bugs or edge cases
  • Evaluate performance considerations
  • Check for security concerns
  • Review test coverage adequacy
  • Provide comprehensive feedback

Review Summary

This is a well-executed bug fix that addresses a specific edge case with _count aggregation in self-referential relations on delegate models. The fix is surgical, well-reasoned, and follows existing patterns in the codebase.

Strengths

1. Correct Root Cause Analysis

  • The issue description accurately identifies the problem: fieldModel === model causing both sides of the correlated WHERE clause to resolve to the same table
  • The fix directly addresses this by generating a unique alias via tmpAlias()

2. Consistent with Existing Patterns

  • Uses the same pattern already established in buildToOneRelationFilter (line 328) and buildRelationJoinFilter methods
  • Follows the codebase convention of using tmpAlias() for subquery disambiguation

3. Comprehensive Test Coverage

  • The regression test in issue-2452.test.ts perfectly reproduces the bug scenario
  • Tests the exact case mentioned in the issue: delegate model with self-referential relation
  • Validates the fix works correctly (expects 2 replies, not 0)

4. Clean Implementation

  • Minimal, focused changes that don't affect other functionality
  • Good code comments explaining the reasoning
  • No breaking changes to existing APIs

🔧 Technical Analysis

Change Breakdown:

// Before: Using fieldModel directly as alias
fieldCountQuery = this.buildModelSelect(fieldModel, fieldModel, value as any, false)

// After: Using unique subquery alias
const subQueryAlias = tmpAlias(`${parentAlias}$_${field}$count`);
fieldCountQuery = this.buildModelSelect(fieldModel, subQueryAlias, value as any, false)

The fix correctly handles both many-to-many and one-to-many relations by:

  1. Generating a unique alias for the subquery (packages/orm/src/client/crud/dialects/base-dialect.ts:1297)
  2. Using this alias consistently in buildModelSelect, join references, and buildJoinPairs calls
  3. Maintaining proper correlation between outer and inner query tables

🎯 Quality Assessments

Performance:No Impact

  • Change only affects alias generation - no additional queries or complexity
  • tmpAlias() is a lightweight string generation utility

Security:No Concerns

  • No user input processing or SQL injection vectors
  • Internal aliasing mechanism with controlled inputs

Backward Compatibility:Preserved

  • No changes to public APIs or behavior for existing working cases
  • Only fixes broken edge case behavior

Edge Cases:Well Handled

  • Fix specifically targets the problematic case (self-referential + delegate models)
  • Regular relations unaffected
  • Many-to-many relations handled correctly

📋 Minor Observations

Code Style: The code follows established patterns and formatting. The comment on line 1295-1296 could be slightly more concise, but it's clear and helpful.

Test Quality: The regression test is excellent - it creates the exact scenario (delegate model + self-referential relation) and verifies the fix works. Consider adding a test case for many-to-many self-referential relations as well, though the current fix should handle both.

Recommendation

APPROVE - This is a high-quality bug fix that:

The implementation is solid and ready for merge.


@ymc9 ymc9 merged commit e9482fe into dev Apr 4, 2026
8 checks passed
@ymc9 ymc9 deleted the fix/delegate-count-self-referential branch April 4, 2026 01:16
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.

Bug Report: _count Not Working for Self-Referential Relations in Delegate Models

1 participant