Skip to content

Conversation

@mag123c
Copy link
Contributor

@mag123c mag123c commented Sep 20, 2025

Description of change

This change fixes an issue where leftJoin combined with skip/take pagination was not properly loading related entities due to missing primary keys in the pagination subquery.

The SelectQueryBuilder uses a two-query approach for pagination with joins to avoid N+1 problems. The first query selects primary keys with pagination, and the second query loads full data for those keys. The bug was in the first query not including joined entity primary keys, causing entity identification issues.

Pull-Request Checklist

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 20, 2025

commit: 9064fa0

@mag123c mag123c force-pushed the fix-leftjoin-pagination-issue-11662 branch from 595ec51 to 02e2d70 Compare September 20, 2025 15:14
@coveralls
Copy link

coveralls commented Sep 20, 2025

Coverage Status

coverage: 80.808% (+0.001%) from 80.807%
when pulling 9064fa0 on mag123c:fix-leftjoin-pagination-issue-11662
into 12fad03 on typeorm:master.

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 @mag123c
May I ask you to solve the conflicts please?

@mag123c mag123c force-pushed the fix-leftjoin-pagination-issue-11662 branch from ff9cbb0 to 196609e Compare December 2, 2025 00:06
@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Dec 2, 2025

PR Code Suggestions ✨

Latest suggestions up to 9064fa0

CategorySuggestion                                                                                                                                    Impact
Possible issue
Verify all composite primary keys

To ensure correct grouping for composite primary keys, change the check from
some to every, requiring all primary key columns to be selected.

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [117-120]

-// Check if primary key columns are actually selected in the raw results
-const primaryKeysSelected = keys.some(
+// Check if all primary key columns are actually selected in the raw results
+const primaryKeysSelected = keys.every(
     (key) => key in (rawResults[0] ?? {}),
 )
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: Using some is incorrect for composite primary keys, as it can lead to incorrect entity grouping if only a partial key is selected. Changing to every ensures that grouping by primary key only occurs when all parts of the key are present, which is critical for data integrity.

High
General
Handle empty results array

Add a check to handle an empty rawResults array at the beginning of the function
to avoid potential errors and improve code clarity.

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [117-122]

+// Handle empty results
+if (rawResults.length === 0) {
+    return map
+}
+
 // Check if primary key columns are actually selected in the raw results
 const primaryKeysSelected = keys.some(
-    (key) => key in (rawResults[0] ?? {}),
+    (key) => key in rawResults[0],
 )
 
 for (const rawResult of rawResults) {
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies a potential issue with an empty rawResults array, but the current code handles this case without error due to optional chaining (?? {}). The proposed change improves explicitness and slightly simplifies the check by removing the need for optional chaining.

Low
  • More

Previous suggestions

Suggestions up to commit 5429b85
CategorySuggestion                                                                                                                                    Impact
Possible issue
Validate all primary keys exist

To ensure correct entity grouping with composite primary keys, change the check
from keys.some() to keys.every(). This validates that all primary key columns
are present in the query result, not just some of them.

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [117-120]

 // Check if primary key columns are actually selected in the raw results
-const primaryKeysSelected = keys.some(
-    (key) => key in (rawResults[0] ?? {}),
-)
+const primaryKeysSelected = rawResults.length > 0 && 
+    keys.every((key) => key in rawResults[0])
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that using keys.some() is insufficient for composite primary keys, as all parts of the key are required for unique identification. Using keys.every() as proposed would make the check more robust and prevent incorrect grouping when only a partial composite primary key is selected.

Medium
Suggestions up to commit dae49c7
CategorySuggestion                                                                                                                                    Impact
General
Optimize row index lookup performance

Replace the inefficient rawResults.indexOf(rawResult) call inside the loop with
a more performant method of getting the row index, such as using the index from
a for...of loop with entries().

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [142-147]

+} else {
+    // Fallback: use row index when primary keys are not available
+    // This ensures each row gets its own group for proper entity mapping
+    const rowIndex = rawResults.indexOf(rawResult)
+    id = `row_${rowIndex}`
+}
 
-
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a performance issue where using indexOf inside a loop leads to O(n^2) complexity. Replacing it with a loop index is a valid optimization that improves efficiency for large result sets.

Low
Suggestions up to commit f94b754
CategorySuggestion                                                                                                                                    Impact
Possible issue
Validate all composite primary keys

Replace keys.some() with keys.every() in the hasValidKeys check to ensure all
parts of a composite primary key are present before attempting to group by key.

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [118-125]

 const hasValidKeys =
     keys.length > 0 &&
     rawResults.length > 0 &&
-    keys.some(
+    keys.every(
         (key) =>
             rawResults[0].hasOwnProperty(key) &&
             rawResults[0][key] !== undefined,
     )
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug in the new hasValidKeys logic where using some instead of every can lead to incorrect entity grouping for entities with composite primary keys when only a partial key is selected.

Medium
Suggestions up to commit 9ddcf86
CategorySuggestion                                                                                                                                    Impact
Possible issue
Validate all composite primary keys

Replace keys.some() with keys.every() to ensure all parts of a composite primary
key are present before attempting to group entities by key. This prevents
incorrect grouping when only a partial key is available.

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [118-125]

 const hasValidKeys =
     keys.length > 0 &&
     rawResults.length > 0 &&
-    keys.some(
+    keys.every(
         (key) =>
             rawResults[0].hasOwnProperty(key) &&
             rawResults[0][key] !== undefined,
     )
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that using some() can lead to incorrect entity grouping with composite primary keys when only a partial key is selected. Changing to every() ensures that grouping by primary key only occurs when the full key is present, making the logic more robust and preventing a potential bug.

Medium
Suggestions up to commit 17cab60
CategorySuggestion                                                                                                                                    Impact
Possible issue
Validate all composite primary keys

Change keys.some() to keys.every() in the hasValidKeys check. This ensures that
grouping by primary key only occurs when all columns of a composite primary key
are present, preventing incorrect data mapping.

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [118-125]

 const hasValidKeys =
     keys.length > 0 &&
     rawResults.length > 0 &&
-    keys.some(
+    keys.every(
         (key) =>
             rawResults[0].hasOwnProperty(key) &&
             rawResults[0][key] !== undefined,
     )
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that using keys.some() can lead to incorrect entity grouping when only a partial composite primary key is selected, which is a subtle but important bug. Changing to keys.every() makes the grouping logic more robust and correct.

Medium
General
Optimize row index lookup performance

Optimize the row index lookup by replacing the for...of loop and indexOf call
with a loop that provides an index, such as forEach or a standard for loop, to
avoid O(n²) complexity.

src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts [147-152]

+} else {
+    // Fallback: use row index when primary keys are not available
+    // This ensures each row gets its own group for proper entity mapping
+    const rowIndex = rawResults.indexOf(rawResult)
+    id = `row_${rowIndex}`
+}
 
-
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out a performance issue with O(n²) complexity by using indexOf inside a for...of loop. While this is a valid optimization, the improved_code is identical to the existing_code and does not provide an actionable fix.

Low

@mag123c
Copy link
Contributor Author

mag123c commented Dec 2, 2025

@gioboa rebased onto master to resolve conflicts.

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 @mag123c

@gioboa gioboa requested review from naorpeled and removed request for naorpeled December 6, 2025 20:30
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 @mag123c for your help

@gioboa gioboa requested a review from naorpeled December 10, 2025 08:25
Copy link
Collaborator

@pkuczynski pkuczynski left a comment

Choose a reason for hiding this comment

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

Looks good. Small suggestion to simplify the code...

Comment on lines 118 to 125
const hasValidKeys =
keys.length > 0 &&
rawResults.length > 0 &&
keys.some(
(key) =>
rawResults[0].hasOwnProperty(key) &&
rawResults[0][key] !== undefined,
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
const hasValidKeys =
keys.length > 0 &&
rawResults.length > 0 &&
keys.some(
(key) =>
rawResults[0].hasOwnProperty(key) &&
rawResults[0][key] !== undefined,
)
const hasValidKeys = keys.some(key => key in (rawResults[0] ?? {}))

Copy link
Collaborator

Choose a reason for hiding this comment

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

hasValidKeys is also a bit misleading/unclear. Should we maybe call it primaryKeysSelected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

primaryKeysSelected much clear than hasValidKeys

@pkuczynski pkuczynski merged commit 4ffe666 into typeorm:master Dec 12, 2025
64 checks passed
mgohin pushed a commit to mgohin/typeorm that referenced this pull request Jan 15, 2026
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.

skip and take don't work for join tables if primary key is not selected

5 participants