Skip to content

Comments

fix: allow selecting @RelationId() fields in find options#11967

Open
yen0304 wants to merge 2 commits intotypeorm:masterfrom
yen0304:fix/relation-id-select-11955
Open

fix: allow selecting @RelationId() fields in find options#11967
yen0304 wants to merge 2 commits intotypeorm:masterfrom
yen0304:fix/relation-id-select-11955

Conversation

@yen0304
Copy link

@yen0304 yen0304 commented Feb 8, 2026

Description of change

findAndCount() and find() throw EntityPropertyNotFoundError when select includes a @RelationId() decorated field.

Root cause: buildSelect() in SelectQueryBuilder validates select properties against columns, embeds, and relations — but @RelationId() properties are none of those. They are stored separately in metadata.relationIds.

Fix: Add a check for metadata.relationIds in buildSelect(). When a @RelationId() property is detected, select the underlying join columns of its associated relation so that RelationIdLoader can populate the value from the raw query results.

How to reproduce (before fix):

@Entity()
class Post {
    @PrimaryGeneratedColumn()
    id: number

    @ManyToOne(() => Category)
    @JoinColumn()
    category: Category

    @RelationId((post: Post) => post.category)
    categoryId: number
}

// This throws EntityPropertyNotFoundError:
await postRepository.findAndCount({
    select: { id: true, name: true, categoryId: true },
    take: 500,
});

Verified: Tests pass against PostgreSQL 17 (Docker).

Pull-Request Checklist

@qodo-free-for-open-source-projects

User description

Description of change

findAndCount() and find() throw EntityPropertyNotFoundError when select includes a @RelationId() decorated field.

Root cause: buildSelect() in SelectQueryBuilder validates select properties against columns, embeds, and relations — but @RelationId() properties are none of those. They are stored separately in metadata.relationIds.

Fix: Add a check for metadata.relationIds in buildSelect(). When a @RelationId() property is detected, select the underlying join columns of its associated relation so that RelationIdLoader can populate the value from the raw query results.

How to reproduce (before fix):

@Entity()
class Post {
    @PrimaryGeneratedColumn()
    id: number

    @ManyToOne(() => Category)
    @JoinColumn()
    category: Category

    @RelationId((post: Post) => post.category)
    categoryId: number
}

// This throws EntityPropertyNotFoundError:
await postRepository.findAndCount({
    select: { id: true, name: true, categoryId: true },
    take: 500,
});

Verified: Tests pass against PostgreSQL 17 (Docker).

Pull-Request Checklist


PR Type

Bug fix


Description

  • Allow selecting @RelationId() decorated fields in find options

  • Add validation check for relationIds in buildSelect() method

  • Select underlying join columns when @RelationId() property detected

  • Add comprehensive test coverage for relation ID selection scenarios


Diagram Walkthrough

flowchart LR
  A["find/findAndCount with select"] --> B["buildSelect() validation"]
  B --> C["Check relationIds metadata"]
  C --> D["Select join columns"]
  D --> E["RelationIdLoader populates value"]
Loading

File Walkthrough

Relevant files
Bug fix
SelectQueryBuilder.ts
Add @RelationId() support to buildSelect validation           

src/query-builder/SelectQueryBuilder.ts

  • Add check for metadata.relationIds in property validation logic
  • Detect @RelationId() properties by matching propertyName
  • Select underlying join columns for ManyToOne and OneToOneOwner
    relations
  • Ensure RelationIdLoader can access values from raw query results
+17/-1   
Tests
Category.ts
Add Category test entity                                                                 

test/functional/find-options/relation-id-select/entity/Category.ts

  • Create test entity Category with id and name columns
  • Used as relation target for Post entity in test scenarios
+12/-0   
Post.ts
Add Post test entity with RelationId field                             

test/functional/find-options/relation-id-select/entity/Post.ts

  • Create test entity Post with id, title, and category relation
  • Add @RelationId() decorated categoryId field
  • Demonstrates the bug scenario and validates the fix
+23/-0   
find-options-relation-id-select.test.ts
Add comprehensive RelationId selection test suite               

test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts

  • Add four test cases covering find() and findAndCount() methods
  • Test selecting @RelationId() fields with other columns
  • Test selecting only id and @RelationId() field
  • Verify correct values are populated in query results
+111/-0 

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 8, 2026

PR Code Suggestions ✨

Latest suggestions up to 895991e

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fail on unsupported relation types

Throw an error when a user tries to select a @RelationId on an unsupported
relation type, such as OneToMany or ManyToMany, instead of failing silently.

src/query-builder/SelectQueryBuilder.ts [4089-4097]

 } else if (relationId) {
     if (
         relationId.relation.isManyToOne ||
         relationId.relation.isOneToOneOwner
     ) {
         for (const joinColumn of relationId.relation.joinColumns) {
             this.selects.push(alias + "." + joinColumn.propertyPath)
         }
+    } else {
+        throw new TypeORMError(
+            `Cannot select @RelationId() field "${propertyPath}" because it is not an owning relation (only ManyToOne / OneToOne owner are supported in select).`,
+        )
     }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that selecting a @RelationId on an unsupported relation type fails silently, and proposing to throw an error improves robustness and developer experience.

Medium
General
Prevent duplicate select expressions

Prevent duplicate select expressions by checking if an expression already exists
in this.selects before adding it.

src/query-builder/SelectQueryBuilder.ts [4089-4097]

 } else if (relationId) {
     if (
         relationId.relation.isManyToOne ||
         relationId.relation.isOneToOneOwner
     ) {
         for (const joinColumn of relationId.relation.joinColumns) {
-            this.selects.push(alias + "." + joinColumn.propertyPath)
+            const selectExpr = alias + "." + joinColumn.propertyPath
+            if (!this.selects.includes(selectExpr)) {
+                this.selects.push(selectExpr)
+            }
         }
     }
  • Apply / Chat
Suggestion importance[1-10]: 3

__

Why: While adding a check to prevent duplicate select expressions is a good defensive practice, the scenarios where this would occur are edge cases, making the impact of this change minor.

Low
  • More

Previous suggestions

Suggestions
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fail fast for unsupported relations

Add an else block to throw an error when a user tries to select a @RelationId on
an unsupported relation type (like OneToMany or ManyToMany), instead of silently
failing.

src/query-builder/SelectQueryBuilder.ts [4078-4086]

 } else if (relationId) {
     if (
         relationId.relation.isManyToOne ||
         relationId.relation.isOneToOneOwner
     ) {
         for (const joinColumn of relationId.relation.joinColumns) {
             this.selects.push(alias + "." + joinColumn.propertyPath)
         }
+    } else {
+        throw new TypeORMError(
+            `Cannot select @RelationId() field "${propertyPath}" because relation "${relationId.relation.propertyPath}" does not have join columns on this side.`,
+        )
     }
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out that unsupported @RelationId selections are silently ignored, leading to confusing behavior. Throwing an explicit error improves developer experience and makes the API more robust.

Medium
Match relation-id by full path

Update the @RelationId metadata lookup to also check against the full
propertyPath to correctly handle selections within embedded entities.

src/query-builder/SelectQueryBuilder.ts [4061-4063]

 const relationId = metadata.relationIds.find(
-    (rid) => rid.propertyName === propertyPath,
+    (rid) => (rid as any).propertyPath === propertyPath || rid.propertyName === propertyPath,
 )
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the added logic for finding @RelationId metadata only checks propertyName, which would fail for embedded entities where propertyPath contains prefixes.

Medium

The buildSelect() method in SelectQueryBuilder only checked for columns,
embeds, and relations when validating the select option. Properties
decorated with @RelationId() were not recognized, causing
EntityPropertyNotFoundError when using find() or findAndCount() with
select that includes @RelationId() fields.

Add a check for metadata.relationIds in buildSelect(). When a
@RelationId() property is found, select the underlying join columns of
the associated relation so RelationIdLoader can populate the value.

Closes typeorm#11955
@yen0304 yen0304 force-pushed the fix/relation-id-select-11955 branch from bb07f38 to f3278d7 Compare February 8, 2026 15:55
@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 8, 2026

PR Code Suggestions ✨

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fail fast for unsupported relations

Add an else block to throw an error when a user tries to select a @RelationId on
an unsupported relation type (like OneToMany or ManyToMany), instead of silently
failing.

src/query-builder/SelectQueryBuilder.ts [4078-4086]

 } else if (relationId) {
     if (
         relationId.relation.isManyToOne ||
         relationId.relation.isOneToOneOwner
     ) {
         for (const joinColumn of relationId.relation.joinColumns) {
             this.selects.push(alias + "." + joinColumn.propertyPath)
         }
+    } else {
+        throw new TypeORMError(
+            `Cannot select @RelationId() field "${propertyPath}" because relation "${relationId.relation.propertyPath}" does not have join columns on this side.`,
+        )
     }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out that unsupported @RelationId selections are silently ignored, leading to confusing behavior. Throwing an explicit error improves developer experience and makes the API more robust.

Medium
Match relation-id by full path

Update the @RelationId metadata lookup to also check against the full
propertyPath to correctly handle selections within embedded entities.

src/query-builder/SelectQueryBuilder.ts [4061-4063]

 const relationId = metadata.relationIds.find(
-    (rid) => rid.propertyName === propertyPath,
+    (rid) => (rid as any).propertyPath === propertyPath || rid.propertyName === propertyPath,
 )
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the added logic for finding @RelationId metadata only checks propertyName, which would fail for embedded entities where propertyPath contains prefixes.

Medium
  • More

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 8, 2026

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (6) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Composite join columns lost 🐞 Bug ✓ Correctness
Description
• For composite FK/PK relations, relation.joinColumns can contain multiple columns, but the PR
selects them via joinColumn.propertyPath, which can be identical for multiple join columns. •
buildEscapedEntityColumnSelects resolves selected columns via a Map keyed by
alias.propertyPath, so duplicate propertyPath values collapse to one selected column. •
RelationIdLoader then expects *all* join column values by joinColumn.databaseName in raw
results; missing columns can yield incorrect/undefined @RelationId() hydration.
Code

src/query-builder/SelectQueryBuilder.ts[R4083-4089]

+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
Evidence
The PR selects join columns using joinColumn.propertyPath. For ManyToOne/OneToOne-owner relations,
join columns may be generated as virtual columns with propertyName = relation.propertyName (so
multiple join columns can share the same propertyPath). The query builder maps
expressionMap.selects to actual columns via a Map keyed by ${alias}.${col.propertyPath}, so
those duplicates overwrite each other and only one join column may be selected. But
RelationIdLoader iterates *all* joinColumns and reads raw results using joinColumn.databaseName,
requiring every join column to be present in the SELECT.

src/query-builder/SelectQueryBuilder.ts[4056-4090]
src/metadata-builder/RelationJoinColumnBuilder.ts[121-142]
src/metadata-builder/RelationJoinColumnBuilder.ts[193-204]
src/query-builder/SelectQueryBuilder.ts[2974-2984]
src/query-builder/relation-id/RelationIdLoader.ts[28-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Selecting `@RelationId()` for ManyToOne/OneToOne-owner currently adds required join columns via `alias + '.' + joinColumn.propertyPath`. For composite-key relations, multiple join columns can share the same `propertyPath` (because virtual join columns are created with `propertyName = relation.propertyName`). This causes collisions when mapping selections to columns, so not all join columns are actually selected. `RelationIdLoader` expects every join column value to be present in raw results, so `@RelationId()` hydration can be wrong/undefined.
## Issue Context
- Join columns for ManyToOne/OneToOne-owner can be created as virtual columns with `propertyName = relation.propertyName`.
- Column resolution for partial selects uses a map keyed by `${alias}.${col.propertyPath}`.
- RelationIdLoader reads join column values from raw results using `joinColumn.databaseName`.
## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[2974-2984]
- src/metadata-builder/RelationJoinColumnBuilder.ts[121-142]
- src/metadata-builder/RelationJoinColumnBuilder.ts[193-204]
- src/query-builder/relation-id/RelationIdLoader.ts[28-71]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Non-owner relationId ignored 🐞 Bug ✓ Correctness
Description
• The new relationId handling only adds selects for ManyToOne/OneToOne-owner relations; for other
@RelationId() relation types it adds nothing. • applyFindOptions only calls select(...) when
this.selects.length > 0, so a select projection consisting only of such @RelationId() fields can
be silently ignored. • This can unexpectedly return full entities (extra columns) and defeat the
purpose of projection (potential perf/data-exposure issue).
Code

src/query-builder/SelectQueryBuilder.ts[R4078-4090]

+            } else if (relationId) {
+                // When selecting a @RelationId() property, we need to select
+                // the underlying join columns of the associated relation so
+                // the value is available in the raw query results for
+                // RelationIdLoader to populate.
+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
+                }
Evidence
The PR makes @RelationId() a valid selectable property, but only pushes underlying columns for
ManyToOne/OneToOne-owner. For other relation types, the branch is effectively a no-op. Separately,
applyFindOptions applies a projection only when this.selects has entries; if buildSelect adds
nothing (possible when the selection includes only such relationId fields), the query builder keeps
its default selection (full entity).

src/query-builder/SelectQueryBuilder.ts[4078-4090]
src/query-builder/SelectQueryBuilder.ts[3253-3271]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
For `@RelationId()` selections where the relation is not ManyToOne/OneToOne-owner, `buildSelect` currently adds no selects. Because `applyFindOptions` only calls `select()` when `this.selects.length > 0`, the projection can be ignored (especially when selecting only such relationId fields), resulting in full-entity selection.
## Issue Context
- The PR now recognizes `@RelationId()` properties as selectable.
- The current implementation only adds underlying join columns for ManyToOne/OneToOne-owner.
- Projections are only applied when `this.selects` is non-empty.
## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[3253-3271]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. RelationId select falls back 🐞 Bug ✓ Correctness
Description
buildSelect now treats @RelationId properties as valid, but only adds actual column selections
for ManyToOne/OneToOneOwner; for other relation types it adds nothing. • If the user’s select
contains only such a relation-id property, applyFindOptions won’t call qb.select() (because
this.selects is empty), and query generation falls back to SELECT *. • This violates
FindOptions.select partial-selection semantics and can unexpectedly fetch all columns (performance
+ correctness surprise).
Code

src/query-builder/SelectQueryBuilder.ts[R4078-4086]

+            } else if (relationId) {
+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
+                }
Evidence
The PR change makes @RelationId pass validation but only contributes selections in the
M2O/1:1-owner case. When no selections are contributed, applyFindOptions does not set an explicit
select list, and select-expression construction falls back to * because no entity column selects
are generated when expressionMap.selects is empty.

src/query-builder/SelectQueryBuilder.ts[4048-4101]
src/query-builder/SelectQueryBuilder.ts[3248-3271]
src/query-builder/SelectQueryBuilder.ts[2957-2992]
src/query-builder/SelectQueryBuilder.ts[2255-2328]
src/query-builder/relation-id/RelationIdLoader.ts[114-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`buildSelect` now recognizes `@RelationId` properties, but for relation types other than `ManyToOne` / `OneToOneOwner` it does not add any actual column selections. When a user selects only such a relation-id field, `applyFindOptions` skips calling `select()`, and SQL generation falls back to `SELECT *`, breaking partial-select semantics.
### Issue Context
`RelationIdLoader` supports `OneToMany` / `OneToOneNotOwner` and `ManyToMany` via additional queries, so a `@RelationId` property can be meaningful for these relations. Even if you decide not to fully support them in `FindOptions.select`, silently turning a partial select into `SELECT *` is undesirable.
### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4048-4101]
- src/query-builder/SelectQueryBuilder.ts[3248-3271]
- src/query-builder/SelectQueryBuilder.ts[2255-2328]
- src/query-builder/SelectQueryBuilder.ts[2957-2992]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (4)
4. RelationId-only select misbehaves 🐞 Bug ✓ Correctness
Description
If findOptions.select contains only @RelationId() fields, owner-side relations
(ManyToOne/OneToOneOwner) override the default select with only virtual join columns, so the entity
transformer drops all entities as having no selected columns. For non-owner relations
(OneToMany/ManyToMany), no selects are added so the default select(alias) remains and the caller’s
select constraint is effectively ignored (all columns are returned).
Code

src/query-builder/SelectQueryBuilder.ts[R4072-4079]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )

-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
               throw new EntityPropertyNotFoundError(propertyPath, metadata)

           if (column) {
Evidence
buildSelect now treats @RelationId properties as valid and, for owner-side relations only, adds only
join-column selections; applyFindOptions replaces the default alias selection only when buildSelect
produced at least one select. When only virtual join-columns are selected,
RawSqlResultsToEntityTransformer won’t hydrate any entity columns (it filters out virtual columns)
and will return undefined entities when hasColumns is false, even if relation ids were computed.

src/query-builder/SelectQueryBuilder.ts[4058-4098]
src/query-builder/SelectQueryBuilder.ts[3255-3273]
src/data-source/DataSource.ts[606-614]
src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[472-499]
src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[200-247]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Selecting only `@RelationId()` fields in `findOptions.select` currently leads to inconsistent results:
- For `ManyToOne`/`OneToOneOwner`, `buildSelect` adds only virtual join-columns, the query selection is replaced, and entity hydration drops all rows (no non-virtual selected columns).
- For `OneToMany`/`ManyToMany`, `buildSelect` adds nothing, so the default `select(alias)` remains and the caller’s `select` constraint is ignored.
### Issue Context
`RawSqlResultsToEntityTransformer` hydrates only **non-virtual** columns that are explicitly selected, and returns `undefined` entities when `hasColumns` is false. The default query builder path starts with `select(alias)`.
### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[3238-3273]
- src/query-builder/SelectQueryBuilder.ts[4058-4098]
- src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[200-247]
- src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[472-499]
### Suggested approach
1) After `buildSelect`, detect when `findOptions.select` produced:
 - `this.selects.length === 0`, or
 - only selections that resolve to `ColumnMetadata.isVirtual === true`
 and **fail fast** with a descriptive error (or implement hidden PK selection + stripping).
2) Add functional tests that cover:
 - `select: { relationIdOnly: true }` for `ManyToOne`
 - `select: { relationIdOnly: true }` for `OneToMany`/`ManyToMany`
 asserting the behavior is deterministic (either a clear error or correct returned shape).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Composite join columns lost 🐞 Bug ✓ Correctness
Description
• For composite FK/PK relations, relation.joinColumns can contain multiple columns, but the PR
selects them via joinColumn.propertyPath, which can be identical for multiple join columns. •
buildEscapedEntityColumnSelects resolves selected columns via a Map keyed by
alias.propertyPath, so duplicate propertyPath values collapse to one selected column. •
RelationIdLoader then expects *all* join column values by joinColumn.databaseName in raw
results; missing columns can yield incorrect/undefined @RelationId() hydration.
Code

src/query-builder/SelectQueryBuilder.ts[R4083-4089]

+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
Evidence
The PR selects join columns using joinColumn.propertyPath. For ManyToOne/OneToOne-owner relations,
join columns may be generated as virtual columns with propertyName = relation.propertyName (so
multiple join columns can share the same propertyPath). The query builder maps
expressionMap.selects to actual columns via a Map keyed by ${alias}.${col.propertyPath}, so
those duplicates overwrite each other and only one join column may be selected. But
RelationIdLoader iterates *all* joinColumns and reads raw results using joinColumn.databaseName,
requiring every join column to be present in the SELECT.

src/query-builder/SelectQueryBuilder.ts[4056-4090]
src/metadata-builder/RelationJoinColumnBuilder.ts[121-142]
src/metadata-builder/RelationJoinColumnBuilder.ts[193-204]
src/query-builder/SelectQueryBuilder.ts[2974-2984]
src/query-builder/relation-id/RelationIdLoader.ts[28-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Selecting `@RelationId()` for ManyToOne/OneToOne-owner currently adds required join columns via `alias + '.' + joinColumn.propertyPath`. For composite-key relations, multiple join columns can share the same `propertyPath` (because virtual join columns are created with `propertyName = relation.propertyName`). This causes collisions when mapping selections to columns, so not all join columns are actually selected. `RelationIdLoader` expects every join column value to be present in raw results, so `@RelationId()` hydration can be wrong/undefined.
## Issue Context
- Join columns for ManyToOne/OneToOne-owner can be created as virtual columns with `propertyName = relation.propertyName`.
- Column resolution for partial selects uses a map keyed by `${alias}.${col.propertyPath}`.
- RelationIdLoader reads join column values from raw results using `joinColumn.databaseName`.
## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[2974-2984]
- src/metadata-builder/RelationJoinColumnBuilder.ts[121-142]
- src/metadata-builder/RelationJoinColumnBuilder.ts[193-204]
- src/query-builder/relation-id/RelationIdLoader.ts[28-71]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Non-owner relationId ignored 🐞 Bug ✓ Correctness
Description
• The new relationId handling only adds selects for ManyToOne/OneToOne-owner relations; for other
@RelationId() relation types it adds nothing. • applyFindOptions only calls select(...) when
this.selects.length > 0, so a select projection consisting only of such @RelationId() fields can
be silently ignored. • This can unexpectedly return full entities (extra columns) and defeat the
purpose of projection (potential perf/data-exposure issue).
Code

src/query-builder/SelectQueryBuilder.ts[R4078-4090]

+            } else if (relationId) {
+                // When selecting a @RelationId() property, we need to select
+                // the underlying join columns of the associated relation so
+                // the value is available in the raw query results for
+                // RelationIdLoader to populate.
+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
+                }
Evidence
The PR makes @RelationId() a valid selectable property, but only pushes underlying columns for
ManyToOne/OneToOne-owner. For other relation types, the branch is effectively a no-op. Separately,
applyFindOptions applies a projection only when this.selects has entries; if buildSelect adds
nothing (possible when the selection includes only such relationId fields), the query builder keeps
its default selection (full entity).

src/query-builder/SelectQueryBuilder.ts[4078-4090]
src/query-builder/SelectQueryBuilder.ts[3253-3271]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
For `@RelationId()` selections where the relation is not ManyToOne/OneToOne-owner, `buildSelect` currently adds no selects. Because `applyFindOptions` only calls `select()` when `this.selects.length > 0`, the projection can be ignored (especially when selecting only such relationId fields), resulting in full-entity selection.
## Issue Context
- The PR now recognizes `@RelationId()` properties as selectable.
- The current implementation only adds underlying join columns for ManyToOne/OneToOne-owner.
- Projections are only applied when `this.selects` is non-empty.
## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[3253-3271]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. RelationId select falls back 🐞 Bug ✓ Correctness
Description
buildSelect now treats @RelationId properties as valid, but only adds actual column selections
for ManyToOne/OneToOneOwner; for other relation types it adds nothing. • If the user’s select
contains only such a relation-id property, applyFindOptions won’t call qb.select() (because
this.selects is empty), and query generation falls back to SELECT *. • This violates
FindOptions.select partial-selection semantics and can unexpectedly fetch all columns (performance
+ correctness surprise).
Code

src/query-builder/SelectQueryBuilder.ts[R4078-4086]

+            } else if (relationId) {
+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
+                }
Evidence
The PR change makes @RelationId pass validation but only contributes selections in the
M2O/1:1-owner case. When no selections are contributed, applyFindOptions does not set an explicit
select list, and select-expression construction falls back to * because no entity column selects
are generated when expressionMap.selects is empty.

src/query-builder/SelectQueryBuilder.ts[4048-4101]
src/query-builder/SelectQueryBuilder.ts[3248-3271]
src/query-builder/SelectQueryBuilder.ts[2957-2992]
src/query-builder/SelectQueryBuilder.ts[2255-2328]
src/query-builder/relation-id/RelationIdLoader.ts[114-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`buildSelect` now recognizes `@RelationId` properties, but for relation types other than `ManyToOne` / `OneToOneOwner` it does not add any actual column selections. When a user selects only such a relation-id field, `applyFindOptions` skips calling `select()`, and SQL generation falls back to `SELECT *`, breaking partial-select semantics.
### Issue Context
`RelationIdLoader` supports `OneToMany` / `OneToOneNotOwner` and `ManyToMany` via additional queries, so a `@RelationId` property can be meaningful for these relations. Even if you decide not to fully support them in `FindOptions.select`, silently turning a partial select into `SELECT *` is undesirable.
### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4048-4101]
- src/query-builder/SelectQueryBuilder.ts[3248-3271]
- src/query-builder/SelectQueryBuilder.ts[2255-2328]
- src/query-builder/SelectQueryBuilder.ts[2957-2992]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

8. @RelationId select undocumented 📘 Rule violation ✓ Correctness
Description
• The PR changes runtime behavior so find()/findAndCount() select can include
@RelationId()-decorated properties without throwing, which is user-facing. • The current “Find
Options” docs describe select generically but do not mention @RelationId() fields as
supported/expected, leaving users unaware of the capability. • This can lead to confusion and
repeated bug reports when users rely on docs to decide what can be selected.
Code

src/query-builder/SelectQueryBuilder.ts[R4061-4068]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )

-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
               throw new EntityPropertyNotFoundError(propertyPath, metadata)

           if (column) {
Evidence
Compliance requires documentation updates for user-facing behavior changes. The PR introduces
explicit handling for metadata.relationIds in buildSelect() to allow selecting @RelationId()
properties, while the existing docs for find options select do not mention @RelationId()
fields.

Rule 2: Docs updated for user-facing changes
src/query-builder/SelectQueryBuilder.ts[4056-4090]
docs/docs/working-with-entity-manager/3-find-options.md[5-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds user-facing support for including `@RelationId()` properties in `find*` `select`, but documentation doesn’t mention this capability.
## Issue Context
`SelectQueryBuilder.buildSelect()` now recognizes `metadata.relationIds` and selects underlying join columns for `many-to-one` / `one-to-one owner` so `RelationIdLoader` can populate the value.
## Fix Focus Areas
- docs/docs/working-with-entity-manager/3-find-options.md[5-30]
- docs/docs/help/3-decorator-reference.md[535-566]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. @RelationId select undocumented 📘 Rule violation ✓ Correctness
Description
• The PR changes SelectQueryBuilder.buildSelect() to treat @RelationId() properties as valid
select targets, which is a user-visible behavior change for find()/findAndCount(). • The docs
section describing select in Find Options does not mention @RelationId() properties, so users
may still assume selecting them is unsupported. • This risks confusion and repeated bug reports for
a supported pattern.
Code

src/query-builder/SelectQueryBuilder.ts[R4061-4068]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )

-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
               throw new EntityPropertyNotFoundError(propertyPath, metadata)

           if (column) {
Evidence
Compliance ID 2 requires documentation updates for user-facing changes. The code now explicitly
accepts metadata.relationIds in buildSelect(), enabling select to include @RelationId()
fields, but the Find Options docs’ select section does not document this capability.

Rule 2: Docs updated for user-facing changes
src/query-builder/SelectQueryBuilder.ts[4061-4086]
docs/docs/working-with-entity-manager/3-find-options.md[5-16]
docs/docs/help/3-decorator-reference.md[535-550]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR enables selecting `@RelationId()` properties via Find Options `select`, but the documentation does not mention this supported usage.
## Issue Context
`buildSelect()` now accepts `metadata.relationIds` and selects underlying join columns so `RelationIdLoader` can populate values. This is a user-facing behavior change and should be documented.
## Fix Focus Areas
- docs/docs/working-with-entity-manager/3-find-options.md[5-16]
- docs/docs/help/3-decorator-reference.md[535-568]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. No #11955 test reference 📘 Rule violation ✓ Correctness
Description
• The new functional test suite for this issue does not include a GitHub issue #11955 reference in
the test file (comment or describe text). • The functional test suite commonly annotates
issue-driven regressions with an issue number to aid triage and future maintenance.
Code

test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[R12-13]

+describe("find options > select with @RelationId", () => {
+    let dataSources: DataSource[]
Evidence
Compliance ID 3 requests functional tests with issue references when applicable. The PR is
explicitly tied to issue #11955, but the added functional test suite header does not reference that
issue number, unlike other functional tests in the repo.

Rule 3: Prefer functional tests over per-issue tests
test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]
test/functional/columns/embedded-columns/columns-embedded-columns.test.ts[166-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new functional test suite added for this fix does not reference the originating issue number (#11955), reducing traceability.
## Issue Context
Other functional tests frequently include `// GitHub issue #NNNNN` comments for issue-driven regressions.
## Fix Focus Areas
- test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
11. @RelationId select undocumented 📘 Rule violation ✓ Correctness
Description
• The PR changes runtime behavior so find()/findAndCount() select can include
@RelationId()-decorated properties without throwing, which is user-facing. • The current “Find
Options” docs describe select generically but do not mention @RelationId() fields as
supported/expected, leaving users unaware of the capability. • This can lead to confusion and
repeated bug reports when users rely on docs to decide what can be selected.
Code

src/query-builder/SelectQueryBuilder.ts[R4061-4068]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )
-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
              throw new EntityPropertyNotFoundError(propertyPath, metadata)
          if (column) {
Evidence
Compliance requires documentation updates for user-facing behavior changes. The PR introduces
explicit handling for metadata.relationIds in buildSelect() to allow selecting @RelationId()
properties, while the existing docs for find options select do not mention @RelationId()
fields.

Rule 2: Docs updated for user-facing changes
src/query-builder/SelectQueryBuilder.ts[4056-4090]
docs/docs/working-with-entity-manager/3-find-options.md[5-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds user-facing support for including `@RelationId()` properties in `find*` `select`, but documentation doesn’t mention this capability.
## Issue Context
`SelectQueryBuilder.buildSelect()` now recognizes `metadata.relationIds` and selects underlying join columns for `many-to-one` / `one-to-one owner` so `RelationIdLoader` can populate the value.
## Fix Focus Areas
- docs/docs/working-with-entity-manager/3-find-options.md[5-30]
- docs/docs/help/3-decorator-reference.md[535-566]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. @RelationId select undocumented 📘 Rule violation ✓ Correctness
Description
• The PR changes SelectQueryBuilder.buildSelect() to treat @RelationId() properties as valid
select targets, which is a user-visible behavior change for find()/findAndCount(). • The docs
section describing select in Find Options does not mention @RelationId() properties, so users
may still assume selecting them is unsupported. • This risks confusion and repeated bug reports for
a supported pattern.
Code

src/query-builder/SelectQueryBuilder.ts[R4061-4068]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )
-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
              throw new EntityPropertyNotFoundError(propertyPath, metadata)
          if (column) {
Evidence
Compliance ID 2 requires documentation updates for user-facing changes. The code now explicitly
accepts metadata.relationIds in buildSelect(), enabling select to include @RelationId()
fields, but the Find Options docs’ select section does not document this capability.

Rule 2: Docs updated for user-facing changes
src/query-builder/SelectQueryBuilder.ts[4061-4086]
docs/docs/working-with-entity-manager/3-find-options.md[5-16]
docs/docs/help/3-decorator-reference.md[535-550]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR enables selecting `@RelationId()` properties via Find Options `select`, but the documentation does not mention this supported usage.
## Issue Context
`buildSelect()` now accepts `metadata.relationIds` and selects underlying join columns so `RelationIdLoader` can populate values. This is a user-facing behavior change and should be documented.
## Fix Focus Areas
- docs/docs/working-with-entity-manager/3-find-options.md[5-16]
- docs/docs/help/3-decorator-reference.md[535-568]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. No #11955 test reference 📘 Rule violation ✓ Correctness
Description
• The new functional test suite for this issue does not include a GitHub issue #11955 reference in
the test file (comment or describe text). • The functional test suite commonly annotates
issue-driven regressions with an issue number to aid triage and future maintenance.
Code

test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[R12-13]

+describe("find options > select with @RelationId", () => {
+    let dataSources: DataSource[]
Evidence
Compliance ID 3 requests functional tests with issue references when applicable. The PR is
explicitly tied to issue #11955, but the added functional test suite header does not reference that
issue number, unlike other functional tests in the repo.

Rule 3: Prefer functional tests over per-issue tests
test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]
test/functional/columns/embedded-columns/columns-embedded-columns.test.ts[166-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new functional test suite added for this fix does not reference the originating issue number (#11955), reducing traceability.
## Issue Context
Other functional tests frequently include `// GitHub issue #NNNNN` comments for issue-driven regressions.
## Fix Focus Areas
- test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +4083 to +4089
if (
relationId.relation.isManyToOne ||
relationId.relation.isOneToOneOwner
) {
for (const joinColumn of relationId.relation.joinColumns) {
this.selects.push(alias + "." + joinColumn.propertyPath)
}

Choose a reason for hiding this comment

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

Action required

1. Composite join columns lost 🐞 Bug ✓ Correctness

• For composite FK/PK relations, relation.joinColumns can contain multiple columns, but the PR
  selects them via joinColumn.propertyPath, which can be identical for multiple join columns.
• buildEscapedEntityColumnSelects resolves selected columns via a Map keyed by
  alias.propertyPath, so duplicate propertyPath values collapse to one selected column.
• RelationIdLoader then expects *all* join column values by joinColumn.databaseName in raw
  results; missing columns can yield incorrect/undefined @RelationId() hydration.
Agent Prompt
## Issue description
Selecting `@RelationId()` for ManyToOne/OneToOne-owner currently adds required join columns via `alias + '.' + joinColumn.propertyPath`. For composite-key relations, multiple join columns can share the same `propertyPath` (because virtual join columns are created with `propertyName = relation.propertyName`). This causes collisions when mapping selections to columns, so not all join columns are actually selected. `RelationIdLoader` expects every join column value to be present in raw results, so `@RelationId()` hydration can be wrong/undefined.

## Issue Context
- Join columns for ManyToOne/OneToOne-owner can be created as virtual columns with `propertyName = relation.propertyName`.
- Column resolution for partial selects uses a map keyed by `${alias}.${col.propertyPath}`.
- RelationIdLoader reads join column values from raw results using `joinColumn.databaseName`.

## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[2974-2984]
- src/metadata-builder/RelationJoinColumnBuilder.ts[121-142]
- src/metadata-builder/RelationJoinColumnBuilder.ts[193-204]
- src/query-builder/relation-id/RelationIdLoader.ts[28-71]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 4078 to 4090
} else if (relationId) {
// When selecting a @RelationId() property, we need to select
// the underlying join columns of the associated relation so
// the value is available in the raw query results for
// RelationIdLoader to populate.
if (
relationId.relation.isManyToOne ||
relationId.relation.isOneToOneOwner
) {
for (const joinColumn of relationId.relation.joinColumns) {
this.selects.push(alias + "." + joinColumn.propertyPath)
}
}

Choose a reason for hiding this comment

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

Action required

2. Non-owner relationid ignored 🐞 Bug ✓ Correctness

• The new relationId handling only adds selects for ManyToOne/OneToOne-owner relations; for other
  @RelationId() relation types it adds nothing.
• applyFindOptions only calls select(...) when this.selects.length > 0, so a select projection
  consisting only of such @RelationId() fields can be silently ignored.
• This can unexpectedly return full entities (extra columns) and defeat the purpose of projection
  (potential perf/data-exposure issue).
Agent Prompt
## Issue description
For `@RelationId()` selections where the relation is not ManyToOne/OneToOne-owner, `buildSelect` currently adds no selects. Because `applyFindOptions` only calls `select()` when `this.selects.length > 0`, the projection can be ignored (especially when selecting only such relationId fields), resulting in full-entity selection.

## Issue Context
- The PR now recognizes `@RelationId()` properties as selectable.
- The current implementation only adds underlying join columns for ManyToOne/OneToOne-owner.
- Projections are only applied when `this.selects` is non-empty.

## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[3253-3271]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. RelationId select falls back 🐞 Bug ✓ Correctness
Description
buildSelect now treats @RelationId properties as valid, but only adds actual column selections
  for ManyToOne/OneToOneOwner; for other relation types it adds nothing.
• If the user’s select contains only such a relation-id property, applyFindOptions won’t call
  qb.select() (because this.selects is empty), and query generation falls back to SELECT *.
• This violates FindOptions.select partial-selection semantics and can unexpectedly fetch all
  columns (performance + correctness surprise).
Code

src/query-builder/SelectQueryBuilder.ts[R4078-4086]

+            } else if (relationId) {
+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
+                }
Evidence
The PR change makes @RelationId pass validation but only contributes selections in the
M2O/1:1-owner case. When no selections are contributed, applyFindOptions does not set an explicit
select list, and select-expression construction falls back to * because no entity column selects
are generated when expressionMap.selects is empty.

src/query-builder/SelectQueryBuilder.ts[4048-4101]
src/query-builder/SelectQueryBuilder.ts[3248-3271]
src/query-builder/SelectQueryBuilder.ts[2957-2992]
src/query-builder/SelectQueryBuilder.ts[2255-2328]
src/query-builder/relation-id/RelationIdLoader.ts[114-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`buildSelect` now recognizes `@RelationId` properties, but for relation types other than `ManyToOne` / `OneToOneOwner` it does not add any actual column selections. When a user selects only such a relation-id field, `applyFindOptions` skips calling `select()`, and SQL generation falls back to `SELECT *`, breaking partial-select semantics.

### Issue Context
`RelationIdLoader` supports `OneToMany` / `OneToOneNotOwner` and `ManyToMany` via additional queries, so a `@RelationId` property can be meaningful for these relations. Even if you decide not to fully support them in `FindOptions.select`, silently turning a partial select into `SELECT *` is undesirable.

### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4048-4101]
- src/query-builder/SelectQueryBuilder.ts[3248-3271]
- src/query-builder/SelectQueryBuilder.ts[2255-2328]
- src/query-builder/SelectQueryBuilder.ts[2957-2992]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. @RelationId select undocumented 📘 Rule violation ✧ Quality
Description
• The PR changes SelectQueryBuilder.buildSelect() to treat @RelationId() properties as valid
  select targets, which is a user-visible behavior change for find()/findAndCount().
• The docs section describing select in Find Options does not mention @RelationId() properties,
  so users may still assume selecting them is unsupported.
• This risks confusion and repeated bug reports for a supported pattern.
Code

src/query-builder/SelectQueryBuilder.ts[R4061-4068]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )

-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
                throw new EntityPropertyNotFoundError(propertyPath, metadata)

            if (column) {
Evidence
Compliance ID 2 requires documentation updates for user-facing changes. The code now explicitly
accepts metadata.relationIds in buildSelect(), enabling select to include @RelationId()
fields, but the Find Options docs’ select section does not document this capability.

Rule 2: Docs updated for user-facing changes
src/query-builder/SelectQueryBuilder.ts[4061-4086]
docs/docs/working-with-entity-manager/3-find-options.md[5-16]
docs/docs/help/3-decorator-reference.md[535-550]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR enables selecting `@RelationId()` properties via Find Options `select`, but the documentation does not mention this supported usage.

## Issue Context
`buildSelect()` now accepts `metadata.relationIds` and selects underlying join columns so `RelationIdLoader` can populate values. This is a user-facing behavior change and should be documented.

## Fix Focus Areas
- docs/docs/working-with-entity-manager/3-find-options.md[5-16]
- docs/docs/help/3-decorator-reference.md[535-568]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. No #11955 test reference 📘 Rule violation ✧ Quality
Description
• The new functional test suite for this issue does not include a GitHub issue #11955 reference in
  the test file (comment or describe text).
• The functional test suite commonly annotates issue-driven regressions with an issue number to aid
  triage and future maintenance.
Code

test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[R12-13]

+describe("find options > select with @RelationId", () => {
+    let dataSources: DataSource[]
Evidence
Compliance ID 3 requests functional tests with issue references when applicable. The PR is
explicitly tied to issue #11955, but the added functional test suite header does not reference that
issue number, unlike other functional tests in the repo.

Rule 3: Prefer functional tests over per-issue tests
test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]
test/functional/columns/embedded-columns/columns-embedded-columns.test.ts[166-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new functional test suite added for this fix does not reference the originating issue number (#11955), reducing traceability.

## Issue Context
Other functional tests frequently include `// GitHub issue #NNNNN` comments for issue-driven regressions.

## Fix Focus Areas
- test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +4078 to +4086
} else if (relationId) {
if (
relationId.relation.isManyToOne ||
relationId.relation.isOneToOneOwner
) {
for (const joinColumn of relationId.relation.joinColumns) {
this.selects.push(alias + "." + joinColumn.propertyPath)
}
}

Choose a reason for hiding this comment

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

Action required

1. Relationid select falls back 🐞 Bug ✓ Correctness

buildSelect now treats @RelationId properties as valid, but only adds actual column selections
  for ManyToOne/OneToOneOwner; for other relation types it adds nothing.
• If the user’s select contains only such a relation-id property, applyFindOptions won’t call
  qb.select() (because this.selects is empty), and query generation falls back to SELECT *.
• This violates FindOptions.select partial-selection semantics and can unexpectedly fetch all
  columns (performance + correctness surprise).
Agent Prompt
### Issue description
`buildSelect` now recognizes `@RelationId` properties, but for relation types other than `ManyToOne` / `OneToOneOwner` it does not add any actual column selections. When a user selects only such a relation-id field, `applyFindOptions` skips calling `select()`, and SQL generation falls back to `SELECT *`, breaking partial-select semantics.

### Issue Context
`RelationIdLoader` supports `OneToMany` / `OneToOneNotOwner` and `ManyToMany` via additional queries, so a `@RelationId` property can be meaningful for these relations. Even if you decide not to fully support them in `FindOptions.select`, silently turning a partial select into `SELECT *` is undesirable.

### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4048-4101]
- src/query-builder/SelectQueryBuilder.ts[3248-3271]
- src/query-builder/SelectQueryBuilder.ts[2255-2328]
- src/query-builder/SelectQueryBuilder.ts[2957-2992]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 20, 2026

commit: 895991e

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

Thanks @yen0304 can you address the qodo suggestions?

@qodo-free-for-open-source-projects

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (3) 📎 Requirement gaps (0)

Grey Divider


Action required

1. RelationId-only select misbehaves 🐞 Bug ✓ Correctness ⭐ New
Description
If findOptions.select contains only @RelationId() fields, owner-side relations
(ManyToOne/OneToOneOwner) override the default select with only virtual join columns, so the entity
transformer drops all entities as having no selected columns. For non-owner relations
(OneToMany/ManyToMany), no selects are added so the default select(alias) remains and the caller’s
select constraint is effectively ignored (all columns are returned).
Code

src/query-builder/SelectQueryBuilder.ts[R4072-4079]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )

-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
                throw new EntityPropertyNotFoundError(propertyPath, metadata)

            if (column) {
Evidence
buildSelect now treats @RelationId properties as valid and, for owner-side relations only, adds only
join-column selections; applyFindOptions replaces the default alias selection only when buildSelect
produced at least one select. When only virtual join-columns are selected,
RawSqlResultsToEntityTransformer won’t hydrate any entity columns (it filters out virtual columns)
and will return undefined entities when hasColumns is false, even if relation ids were computed.

src/query-builder/SelectQueryBuilder.ts[4058-4098]
src/query-builder/SelectQueryBuilder.ts[3255-3273]
src/data-source/DataSource.ts[606-614]
src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[472-499]
src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[200-247]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Selecting only `@RelationId()` fields in `findOptions.select` currently leads to inconsistent results:
- For `ManyToOne`/`OneToOneOwner`, `buildSelect` adds only virtual join-columns, the query selection is replaced, and entity hydration drops all rows (no non-virtual selected columns).
- For `OneToMany`/`ManyToMany`, `buildSelect` adds nothing, so the default `select(alias)` remains and the caller’s `select` constraint is ignored.

### Issue Context
`RawSqlResultsToEntityTransformer` hydrates only **non-virtual** columns that are explicitly selected, and returns `undefined` entities when `hasColumns` is false. The default query builder path starts with `select(alias)`.

### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[3238-3273]
- src/query-builder/SelectQueryBuilder.ts[4058-4098]
- src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[200-247]
- src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[472-499]

### Suggested approach
1) After `buildSelect`, detect when `findOptions.select` produced:
  - `this.selects.length === 0`, or
  - only selections that resolve to `ColumnMetadata.isVirtual === true`
  and **fail fast** with a descriptive error (or implement hidden PK selection + stripping).
2) Add functional tests that cover:
  - `select: { relationIdOnly: true }` for `ManyToOne`
  - `select: { relationIdOnly: true }` for `OneToMany`/`ManyToMany`
  asserting the behavior is deterministic (either a clear error or correct returned shape).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Composite join columns lost 🐞 Bug ✓ Correctness
Description
• For composite FK/PK relations, relation.joinColumns can contain multiple columns, but the PR
selects them via joinColumn.propertyPath, which can be identical for multiple join columns. •
buildEscapedEntityColumnSelects resolves selected columns via a Map keyed by
alias.propertyPath, so duplicate propertyPath values collapse to one selected column. •
RelationIdLoader then expects *all* join column values by joinColumn.databaseName in raw
results; missing columns can yield incorrect/undefined @RelationId() hydration.
Code

src/query-builder/SelectQueryBuilder.ts[R4083-4089]

+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
Evidence
The PR selects join columns using joinColumn.propertyPath. For ManyToOne/OneToOne-owner relations,
join columns may be generated as virtual columns with propertyName = relation.propertyName (so
multiple join columns can share the same propertyPath). The query builder maps
expressionMap.selects to actual columns via a Map keyed by ${alias}.${col.propertyPath}, so
those duplicates overwrite each other and only one join column may be selected. But
RelationIdLoader iterates *all* joinColumns and reads raw results using joinColumn.databaseName,
requiring every join column to be present in the SELECT.

src/query-builder/SelectQueryBuilder.ts[4056-4090]
src/metadata-builder/RelationJoinColumnBuilder.ts[121-142]
src/metadata-builder/RelationJoinColumnBuilder.ts[193-204]
src/query-builder/SelectQueryBuilder.ts[2974-2984]
src/query-builder/relation-id/RelationIdLoader.ts[28-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Selecting `@RelationId()` for ManyToOne/OneToOne-owner currently adds required join columns via `alias + '.' + joinColumn.propertyPath`. For composite-key relations, multiple join columns can share the same `propertyPath` (because virtual join columns are created with `propertyName = relation.propertyName`). This causes collisions when mapping selections to columns, so not all join columns are actually selected. `RelationIdLoader` expects every join column value to be present in raw results, so `@RelationId()` hydration can be wrong/undefined.
## Issue Context
- Join columns for ManyToOne/OneToOne-owner can be created as virtual columns with `propertyName = relation.propertyName`.
- Column resolution for partial selects uses a map keyed by `${alias}.${col.propertyPath}`.
- RelationIdLoader reads join column values from raw results using `joinColumn.databaseName`.
## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[2974-2984]
- src/metadata-builder/RelationJoinColumnBuilder.ts[121-142]
- src/metadata-builder/RelationJoinColumnBuilder.ts[193-204]
- src/query-builder/relation-id/RelationIdLoader.ts[28-71]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Non-owner relationId ignored 🐞 Bug ✓ Correctness
Description
• The new relationId handling only adds selects for ManyToOne/OneToOne-owner relations; for other
@RelationId() relation types it adds nothing. • applyFindOptions only calls select(...) when
this.selects.length > 0, so a select projection consisting only of such @RelationId() fields can
be silently ignored. • This can unexpectedly return full entities (extra columns) and defeat the
purpose of projection (potential perf/data-exposure issue).
Code

src/query-builder/SelectQueryBuilder.ts[R4078-4090]

+            } else if (relationId) {
+                // When selecting a @RelationId() property, we need to select
+                // the underlying join columns of the associated relation so
+                // the value is available in the raw query results for
+                // RelationIdLoader to populate.
+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
+                }
Evidence
The PR makes @RelationId() a valid selectable property, but only pushes underlying columns for
ManyToOne/OneToOne-owner. For other relation types, the branch is effectively a no-op. Separately,
applyFindOptions applies a projection only when this.selects has entries; if buildSelect adds
nothing (possible when the selection includes only such relationId fields), the query builder keeps
its default selection (full entity).

src/query-builder/SelectQueryBuilder.ts[4078-4090]
src/query-builder/SelectQueryBuilder.ts[3253-3271]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
For `@RelationId()` selections where the relation is not ManyToOne/OneToOne-owner, `buildSelect` currently adds no selects. Because `applyFindOptions` only calls `select()` when `this.selects.length > 0`, the projection can be ignored (especially when selecting only such relationId fields), resulting in full-entity selection.
## Issue Context
- The PR now recognizes `@RelationId()` properties as selectable.
- The current implementation only adds underlying join columns for ManyToOne/OneToOne-owner.
- Projections are only applied when `this.selects` is non-empty.
## Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4056-4090]
- src/query-builder/SelectQueryBuilder.ts[3253-3271]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. RelationId select falls back 🐞 Bug ✓ Correctness
Description
buildSelect now treats @RelationId properties as valid, but only adds actual column selections
for ManyToOne/OneToOneOwner; for other relation types it adds nothing. • If the user’s select
contains only such a relation-id property, applyFindOptions won’t call qb.select() (because
this.selects is empty), and query generation falls back to SELECT *. • This violates
FindOptions.select partial-selection semantics and can unexpectedly fetch all columns (performance
+ correctness surprise).
Code

src/query-builder/SelectQueryBuilder.ts[R4078-4086]

+            } else if (relationId) {
+                if (
+                    relationId.relation.isManyToOne ||
+                    relationId.relation.isOneToOneOwner
+                ) {
+                    for (const joinColumn of relationId.relation.joinColumns) {
+                        this.selects.push(alias + "." + joinColumn.propertyPath)
+                    }
+                }
Evidence
The PR change makes @RelationId pass validation but only contributes selections in the
M2O/1:1-owner case. When no selections are contributed, applyFindOptions does not set an explicit
select list, and select-expression construction falls back to * because no entity column selects
are generated when expressionMap.selects is empty.

src/query-builder/SelectQueryBuilder.ts[4048-4101]
src/query-builder/SelectQueryBuilder.ts[3248-3271]
src/query-builder/SelectQueryBuilder.ts[2957-2992]
src/query-builder/SelectQueryBuilder.ts[2255-2328]
src/query-builder/relation-id/RelationIdLoader.ts[114-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`buildSelect` now recognizes `@RelationId` properties, but for relation types other than `ManyToOne` / `OneToOneOwner` it does not add any actual column selections. When a user selects only such a relation-id field, `applyFindOptions` skips calling `select()`, and SQL generation falls back to `SELECT *`, breaking partial-select semantics.
### Issue Context
`RelationIdLoader` supports `OneToMany` / `OneToOneNotOwner` and `ManyToMany` via additional queries, so a `@RelationId` property can be meaningful for these relations. Even if you decide not to fully support them in `FindOptions.select`, silently turning a partial select into `SELECT *` is undesirable.
### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[4048-4101]
- src/query-builder/SelectQueryBuilder.ts[3248-3271]
- src/query-builder/SelectQueryBuilder.ts[2255-2328]
- src/query-builder/SelectQueryBuilder.ts[2957-2992]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. @RelationId select undocumented 📘 Rule violation ✓ Correctness
Description
• The PR changes runtime behavior so find()/findAndCount() select can include
@RelationId()-decorated properties without throwing, which is user-facing. • The current “Find
Options” docs describe select generically but do not mention @RelationId() fields as
supported/expected, leaving users unaware of the capability. • This can lead to confusion and
repeated bug reports when users rely on docs to decide what can be selected.
Code

src/query-builder/SelectQueryBuilder.ts[R4061-4068]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )

-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
               throw new EntityPropertyNotFoundError(propertyPath, metadata)

           if (column) {
Evidence
Compliance requires documentation updates for user-facing behavior changes. The PR introduces
explicit handling for metadata.relationIds in buildSelect() to allow selecting @RelationId()
properties, while the existing docs for find options select do not mention @RelationId()
fields.

Rule 2: Docs updated for user-facing changes
src/query-builder/SelectQueryBuilder.ts[4056-4090]
docs/docs/working-with-entity-manager/3-find-options.md[5-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds user-facing support for including `@RelationId()` properties in `find*` `select`, but documentation doesn’t mention this capability.
## Issue Context
`SelectQueryBuilder.buildSelect()` now recognizes `metadata.relationIds` and selects underlying join columns for `many-to-one` / `one-to-one owner` so `RelationIdLoader` can populate the value.
## Fix Focus Areas
- docs/docs/working-with-entity-manager/3-find-options.md[5-30]
- docs/docs/help/3-decorator-reference.md[535-566]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. @RelationId select undocumented 📘 Rule violation ✓ Correctness
Description
• The PR changes SelectQueryBuilder.buildSelect() to treat @RelationId() properties as valid
select targets, which is a user-visible behavior change for find()/findAndCount(). • The docs
section describing select in Find Options does not mention @RelationId() properties, so users
may still assume selecting them is unsupported. • This risks confusion and repeated bug reports for
a supported pattern.
Code

src/query-builder/SelectQueryBuilder.ts[R4061-4068]

+            const relationId = metadata.relationIds.find(
+                (rid) => rid.propertyName === propertyPath,
+            )

-            if (!embed && !column && !relation)
+            if (!embed && !column && !relation && !relationId)
               throw new EntityPropertyNotFoundError(propertyPath, metadata)

           if (column) {
Evidence
Compliance ID 2 requires documentation updates for user-facing changes. The code now explicitly
accepts metadata.relationIds in buildSelect(), enabling select to include @RelationId()
fields, but the Find Options docs’ select section does not document this capability.

Rule 2: Docs updated for user-facing changes
src/query-builder/SelectQueryBuilder.ts[4061-4086]
docs/docs/working-with-entity-manager/3-find-options.md[5-16]
docs/docs/help/3-decorator-reference.md[535-550]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR enables selecting `@RelationId()` properties via Find Options `select`, but the documentation does not mention this supported usage.
## Issue Context
`buildSelect()` now accepts `metadata.relationIds` and selects underlying join columns so `RelationIdLoader` can populate values. This is a user-facing behavior change and should be documented.
## Fix Focus Areas
- docs/docs/working-with-entity-manager/3-find-options.md[5-16]
- docs/docs/help/3-decorator-reference.md[535-568]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. No #11955 test reference 📘 Rule violation ✓ Correctness
Description
• The new functional test suite for this issue does not include a GitHub issue #11955 reference in
the test file (comment or describe text). • The functional test suite commonly annotates
issue-driven regressions with an issue number to aid triage and future maintenance.
Code

test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[R12-13]

+describe("find options > select with @RelationId", () => {
+    let dataSources: DataSource[]
Evidence
Compliance ID 3 requests functional tests with issue references when applicable. The PR is
explicitly tied to issue #11955, but the added functional test suite header does not reference that
issue number, unlike other functional tests in the repo.

Rule 3: Prefer functional tests over per-issue tests
test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]
test/functional/columns/embedded-columns/columns-embedded-columns.test.ts[166-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new functional test suite added for this fix does not reference the originating issue number (#11955), reducing traceability.
## Issue Context
Other functional tests frequently include `// GitHub issue #NNNNN` comments for issue-driven regressions.
## Fix Focus Areas
- test/functional/find-options/relation-id-select/find-options-relation-id-select.test.ts[12-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

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.

Cannot select @RelationId() fields in findAndCount() — Property "categoryId" not found

2 participants