Skip to content

fix: treat a null local key as no related rows across all relation types - #1189

Open
evoactivity wants to merge 3 commits into
adonisjs:22.xfrom
evoactivity:fix/nullable-local-key
Open

fix: treat a null local key as no related rows across all relation types#1189
evoactivity wants to merge 3 commits into
adonisjs:22.xfrom
evoactivity:fix/nullable-local-key

Conversation

@evoactivity

@evoactivity evoactivity commented Aug 2, 2026

Copy link
Copy Markdown

🔗 Linked issue

Resolves #1188

❓ Type of change

  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

getValue raises for null and undefined alike, so one row with a null local key made preload throw for the entire collection. undefined means the column was never selected, which is a programmer error, while null means the row simply has no related rows.

Adds getNullableValue, which raises only on undefined, and uses it in hasMany, hasOne, hasManyThrough and manyToMany. Null keys are filtered out of the whereIn on the eager branch, and the single-parent branch short-circuits to an empty whereIn.

belongsTo already behaved this way with a nullable foreign key, so this brings the other four in line rather than introducing new behaviour.

Two commits, so the bug is verifiable independently of the fix. Checking out the first alone gives four failing tests:

✔ belongsTo tolerates a null foreign key (existing behaviour)
✖ hasMany tolerates a null local key
✖ hasOne tolerates a null local key
✖ manyToMany tolerates a null local key
✖ hasManyThrough tolerates a null local key
✔ an unselected local key still raises

The belongsTo test and the undefined-key test pass either way, pinning the behaviour being matched and the diagnostic that must not be swallowed. The tests use the nullable tenant_id columns already present in test-helpers, so there are no schema changes.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

No documentation change: this aligns four relation types with the behaviour belongsTo already exhibits.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed relationship queries involving null local keys across has-many, has-one, many-to-many, and through relationships.
    • Such relationships now return empty results instead of applying incorrect null-value filters.
    • Preserved error handling when required relationship keys are not selected.
  • Tests

    • Added coverage for nullable local-key relationship behavior.

belongsTo filters null foreign keys out of the where-in and raises only
on undefined. hasMany, hasOne, hasManyThrough and manyToMany raise on
both, so one row with a null local key breaks the whole preload.

These tests use the existing nullable tenant_id columns, so they need no
schema changes. Four of them fail. The belongsTo test and the
undefined-key test pass, pinning the behaviour the fix has to match and
the diagnostic it must not swallow.
getValue raises for null and undefined alike, so a single row with a
null local key made preload throw for the whole collection. The two
cases mean different things: undefined means the column was never
selected, which is a programmer error, while null means the row has no
related rows, which is ordinary.

Adds getNullableValue, which raises only on undefined, and uses it in
hasMany, hasOne, hasManyThrough and manyToMany. Null keys are filtered
out of the where-in for the eager branch, and the single-parent branch
short-circuits to an empty where-in.

belongsTo already did exactly this with a nullable foreign key, so this
brings the other relation types in line rather than introducing a new
behaviour.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 25de6747-ea44-4555-822c-855beb3943ec

📥 Commits

Reviewing files that changed from the base of the PR and between d9e7d98 and e80604c.

📒 Files selected for processing (1)
  • test/orm/nullable_local_key.spec.ts

📝 Walkthrough

Walkthrough

Nullable local keys now return empty related results for hasMany, hasOne, hasManyThrough, and manyToMany. Undefined local keys still raise the existing error. Tests cover both behaviors.

Changes

Nullable relationship local keys

Layer / File(s) Summary
Nullable value extraction
src/utils/index.ts
Adds getNullableValue, which preserves null and retains errors for undefined keys.
Relation query constraints
src/orm/relations/*/query_builder.ts
Updates eager and single-parent constraints to exclude null keys or use empty whereIn conditions across four relation types.
Nullable local key validation
test/orm/nullable_local_key.spec.ts
Tests empty related results for null keys and errors for unselected local keys.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ParentModel
  participant RelationQueryBuilder
  participant RelatedQuery
  ParentModel->>RelationQueryBuilder: resolve local key
  RelationQueryBuilder->>RelationQueryBuilder: preserve null or raise for undefined
  RelationQueryBuilder->>RelatedQuery: apply whereIn with keys or an empty list
  RelatedQuery-->>ParentModel: return related results
Loading

Poem

A rabbit checks the keys with care,
Null finds an empty burrow there.
Undefined still sounds the alarm,
Four relations now stay calm.
Tests hop through each relation bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.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 The title clearly describes the main fix for null local keys across all relation types.
Linked Issues check ✅ Passed The changes satisfy #1188: null local keys produce empty or null relations, undefined keys still raise, and all four affected relation types are covered.
Out of Scope Changes check ✅ Passed All changes support #1188, including nullable-key handling, shared utility logic, and focused regression tests.
✨ 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.

@evoactivity
evoactivity marked this pull request as ready for review August 2, 2026 14:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/orm/nullable_local_key.spec.ts (1)

256-281: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend the "unselected local key still raises" test to the other relation types.

This regression test only covers hasMany. hasOne, hasManyThrough, and manyToMany route through the same getNullableValue helper but through separate query builders. Add the same "unselected column still raises" assertion for those three relation types to directly confirm each call site preserves the existing error path for undefined keys.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/orm/nullable_local_key.spec.ts` around lines 256 - 281, Extend the “an
unselected local key still raises” coverage beyond the existing HasMany relation
by adding equivalent assertions for HasOne, HasManyThrough, and ManyToMany.
Define the corresponding relations in the test setup and verify that selecting
only the primary key before preloading each relation rejects with the same
undefined local-key error, exercising each relation query builder.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/orm/nullable_local_key.spec.ts`:
- Around line 256-281: Extend the “an unselected local key still raises”
coverage beyond the existing HasMany relation by adding equivalent assertions
for HasOne, HasManyThrough, and ManyToMany. Define the corresponding relations
in the test setup and verify that selecting only the primary key before
preloading each relation rejects with the same undefined local-key error,
exercising each relation query builder.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8efe1529-a3c9-4baf-9f04-ab3fc37b27c4

📥 Commits

Reviewing files that changed from the base of the PR and between e617683 and d9e7d98.

📒 Files selected for processing (6)
  • src/orm/relations/has_many/query_builder.ts
  • src/orm/relations/has_many_through/query_builder.ts
  • src/orm/relations/has_one/query_builder.ts
  • src/orm/relations/many_to_many/query_builder.ts
  • src/utils/index.ts
  • test/orm/nullable_local_key.spec.ts

Each relation type routes through its own query builder, so the shared
helper covering one of them does not prove the other three keep the
error path. Asserts all four rather than assuming.
@evoactivity

evoactivity commented Aug 2, 2026

Copy link
Copy Markdown
Author

Coderabbit review addressed in e80604c.

The undefined-key regression test now asserts all four relation types rather than just hasMany. Each routes through its own query builder, so the shared helper covering one does not prove the other three keep the error path. The assertion matches on the relation name in the message, so a silent no-raise fails rather than passing quietly.

Also added the missing docstring on the test's boot helper for the docstring coverage check.

edit: maybe the docstring coverage check is pre-existing? Didn't make a difference adding it, it's in a test anyway so was a bit of a guess where I needed to add it.

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.

Preload raises on a null local key for hasMany, hasOne, hasManyThrough and manyToMany

1 participant