Skip to content

Conversation

@xdc0
Copy link

@xdc0 xdc0 commented Mar 13, 2024

Description of change

On #9354 there was a patch to map subquery results to an entity when using the leftJoinAndMapOne and innerJoinAndMapOne methods through a new parameter: mapAsEntity.

This unfortunately does not quite work as expected because the subquery is ignored since the join logic detects that there is metadata and the logic will override the subquery in favor of generated queries via metadata.

This problem is described by @cjsauer on the following comment: #9354 (comment)

Fixes #5637

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • npm run format to apply prettier formatting
  • npm run test passes with this change
  • This pull request links relevant issues as Fixes #0000
  • There are new or updated unit tests validating the change
  • Documentation has been updated to reflect this change
  • The new commits follow conventions explained in CONTRIBUTING.md

Summary by CodeRabbit

  • Tests
    • Added new test cases to verify correct behavior when join subqueries with conditions do not find matching data, ensuring left joins map to null and inner joins return no entity.
  • Refactor
    • Improved and unified the internal handling of subqueries within join operations for more consistent and maintainable logic.

@xdc0 xdc0 force-pushed the fix-map-as-entity branch 2 times, most recently from 58c243d to 727d909 Compare March 13, 2024 01:53
@0xtrou
Copy link

0xtrou commented Aug 5, 2024

Need this to get merged asap

1 similar comment
@pzcuong
Copy link

pzcuong commented Aug 8, 2024

Need this to get merged asap

@narimanam
Copy link

any hope on this merge?

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 @xdc0
is this PR still valid? Could you add tests to cover this fix?

@xdc0
Copy link
Author

xdc0 commented Mar 6, 2025

@gioboa It's still be valid. The PR includes test coverage here: https://github.com/typeorm/typeorm/pull/10769/files#diff-fc1093be2aa4e9334350e29375146e0e2cc029295285b80c5caf92dac490c422

Open to comments if it's in need of more tests.

@xdc0 xdc0 requested a review from gioboa May 13, 2025 23:07
@gioboa gioboa requested review from alumni and sgarner May 14, 2025 06:29
@gioboa gioboa added size-s Simple tasks that require minimal effort. Estimated effort: 1-2 hours. and removed Marked For Double Check labels May 14, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 27, 2025

Walkthrough

The changes refactor the internal logic of subquery handling in the join method of the SelectQueryBuilder class to consolidate and simplify subquery detection and processing. Additionally, two new test cases are introduced to verify the behavior of join subqueries with conditions that result in no matching data, ensuring correct handling of null and absent join results.

Changes

File(s) Change Summary
src/query-builder/SelectQueryBuilder.ts Refactored subquery detection and processing in the join method to unify logic and reduce code duplication.
test/functional/query-builder/join/query-builder-joins.ts Added two test cases for left and inner join mapping with subqueries that have no matching join data.

Sequence Diagram(s)

sequenceDiagram
    participant TestCase
    participant QueryBuilder
    participant SubQuery
    participant Database

    TestCase->>QueryBuilder: Initiate join with subquery
    QueryBuilder->>SubQuery: Build subquery (function or string)
    SubQuery-->>QueryBuilder: Return subquery SQL and parameters
    QueryBuilder->>Database: Execute join with subquery
    Database-->>QueryBuilder: Return results (null or no entity)
    QueryBuilder-->>TestCase: Return mapped result
Loading

Poem

In the warren of code, a join took a leap,
Subqueries now tidy, no logic to sweep.
With tests for the cases where matches are none,
Left joins return null, inner joins return none.
A hop and a skip, the builder’s refined—
Clean code and clear tests, all neatly aligned!
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn config production Use --omit=dev instead.
npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-05-27T01_37_06_147Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/query-builder/SelectQueryBuilder.ts (3)

2037-2043: Improve sub-query detection robustness

The heuristic for identifying a string-based sub-query checks only that the first and last characters are "(" / ")". This will fail on otherwise valid inputs such as

"\n( SELECT 1 ) "

or strings padded with comments / leading whitespace.
Consider trimming whitespace (and maybe lower-casing) before the check or, even better, using startsWith("(") / endsWith(")") after a trim() to make the test less brittle.

-const isSubQuery =
-    (!isEntity && typeof entityOrProperty === "function") ||
-    (typeof entityOrProperty === "string" &&
-        entityOrProperty.substr(0, 1) === "(" &&
-        entityOrProperty.substr(-1) === ")")
+const isSubQuery =
+    (!isEntity && typeof entityOrProperty === "function") ||
+    (typeof entityOrProperty === "string" &&
+        entityOrProperty.trim().startsWith("(") &&
+        entityOrProperty.trim().endsWith(")"))

2046-2056: Potential misuse of this when building a sub-query

this is already a SelectQueryBuilder inside the current context, so the double cast

(this as any as SelectQueryBuilder<any>).subQuery()

is unnecessary and can hide compile-time errors. Calling this.subQuery() directly is simpler and safer.

-const subQueryBuilder: SelectQueryBuilder<any> = (
-    entityOrProperty as any
-)((this as any as SelectQueryBuilder<any>).subQuery())
+const subQueryBuilder = (entityOrProperty as any)(this.subQuery())

2071-2075: Pass subQuery unconditionally if already detected

Because isSubQuery is guaranteed to be truthy whenever subQuery is populated, subQuery ? subQuery : undefined is simpler and guards against accidental divergence between the two flags.

-    subQuery: isSubQuery ? subQuery : undefined,
+    subQuery,

(This is purely cosmetic; no behaviour change.)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83567f5 and 727d909.

📒 Files selected for processing (2)
  • src/query-builder/SelectQueryBuilder.ts (2 hunks)
  • test/functional/query-builder/join/query-builder-joins.ts (2 hunks)
🔇 Additional comments (2)
test/functional/query-builder/join/query-builder-joins.ts (2)

573-605: Excellent test case for leftJoinAndMapOne subquery edge case.

This test effectively validates that leftJoinAndMapOne with a subquery properly respects the subquery conditions. The test correctly:

  1. Creates a Tag with name "audi" and links it to a Post
  2. Uses a subquery that filters out the existing tag (tag.name != "audi")
  3. Asserts that the mapped property is null when the subquery finds no matches

This directly addresses the PR objective of ensuring subqueries are not overridden by metadata-based queries in join mapping methods.


1058-1089: Excellent test case for innerJoinAndMapOne subquery edge case.

This test effectively validates that innerJoinAndMapOne with a subquery properly respects the subquery conditions. The test correctly:

  1. Creates a Tag with name "audi" and links it to a Post
  2. Uses a subquery that filters out the existing tag (tag.name != "audi")
  3. Asserts that the entire result is null when the inner join subquery finds no matches

This complements the leftJoinAndMapOne test and ensures both join mapping methods handle subquery conditions correctly, which aligns perfectly with the PR's fix for issue #5637.

Copy link
Collaborator

@sgarner sgarner left a comment

Choose a reason for hiding this comment

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

@xdc0 Thanks for the contribution! 💜

The changes look good. Can you please rebase your branch onto latest master, so that we can run tests?

@xdc0
Copy link
Author

xdc0 commented Jun 27, 2025

@xdc0 Thanks for the contribution! 💜

The changes look good. Can you please rebase your branch onto latest master, so that we can run tests?

will do

@xdc0 xdc0 force-pushed the fix-map-as-entity branch from 727d909 to 755f1bb Compare August 18, 2025 01:35
@alumni
Copy link
Collaborator

alumni commented Aug 18, 2025

@xdc0 could you check if #5637 (comment) solves the original issue for you?

@xdc0 xdc0 force-pushed the fix-map-as-entity branch from 2edda8d to 9cb09d2 Compare August 19, 2025 04:03
@xdc0
Copy link
Author

xdc0 commented Aug 19, 2025

@xdc0 could you check if #5637 (comment) solves the original issue for you?

@alumni Yes, it is still an issue when adding subQuery() to qb:

  query builder > joins
    leftJoinAndMap
      1) should not load join data when join subquery does not find results


  0 passing (133ms)
  1 failing

  1) query builder > joins
       leftJoinAndMap
         should not load join data when join subquery does not find results:
     AssertionError: expected Tag{ id: 1, name: 'audi' } to be null
      at /Users/xdc/projects/typeorm/test/functional/query-builder/join/query-builder-joins.ts:603:50
      at processTicksAndRejections (node:internal/process/task_queues:105:5)
      at async Promise.all (index 0)

Here is the generated sql without my patch (and using subQuery())

SELECT "post"."id" AS "post_id", "post"."title" AS "post_title", "post"."tagId" AS "post_tagId", "post"."authorId" AS "post_authorId", "tag"."id" AS "tag_id", "tag"."name" AS "tag_name" FROM "post" "post" LEFT JOIN "tag" "tag" ON "tag"."id" = "post"."tagId" WHERE "post"."id" = $1

And here is the sql with my patch.

SELECT "post"."id" AS "post_id", "post"."title" AS "post_title", "post"."tagId" AS "post_tagId", "post"."authorId" AS "post_authorId", "tag"."id" AS "tag_id", "tag"."name" AS "tag_name" FROM "post" "post" LEFT JOIN (SELECT * FROM "tag" "tag" WHERE "tag"."name" != $1) "tag" ON "tag"."id" = "post"."tagId" WHERE "post"."id" = $2

@xdc0 xdc0 force-pushed the fix-map-as-entity branch from 9cb09d2 to 6d5c320 Compare August 19, 2025 04:14
@xdc0 xdc0 requested review from alumni and sgarner August 19, 2025 04:14
@xdc0
Copy link
Author

xdc0 commented Aug 19, 2025

Updated fork with upstream.
Updated test to use subQuery.

Ready for another review pass.

@xdc0 xdc0 force-pushed the fix-map-as-entity branch 2 times, most recently from 85b5a3a to 071ea24 Compare August 19, 2025 04:42
@Slugsh
Copy link

Slugsh commented Oct 29, 2025

Still an issue. Any change of this getting merged?

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

@xdc0 xdc0 force-pushed the fix-map-as-entity branch from 071ea24 to d4e7c3d Compare December 9, 2025 20:00
@qodo-free-for-open-source-projects
Copy link

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

PR Code Suggestions ✨

Latest suggestions up to 3e73121

CategorySuggestion                                                                                                                                    Impact
General
Replace deprecated substr method

Replace the deprecated substr() method with modern string indexing for better
code quality and future compatibility.

src/query-builder/SelectQueryBuilder.ts [2098-2103]

 const isEntity = this.connection.hasMetadata(entityOrProperty)
 const isSubQuery =
     (!isEntity && typeof entityOrProperty === "function") ||
     (typeof entityOrProperty === "string" &&
-        entityOrProperty.substr(0, 1) === "(" &&
-        entityOrProperty.substr(-1) === ")")
+        entityOrProperty[0] === "(" &&
+        entityOrProperty[entityOrProperty.length - 1] === ")")
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies the use of a deprecated substr method and proposes a modern, equivalent alternative, which improves code quality and future compatibility.

Low
  • More

Previous suggestions

Suggestions up to commit d4e7c3d
CategorySuggestion                                                                                                                                    Impact
General
Replace deprecated substr method

Replace the deprecated substr() method with charAt() for better code
maintainability and future compatibility.

src/query-builder/SelectQueryBuilder.ts [2099-2104]

 const isEntity = this.connection.hasMetadata(entityOrProperty)
 const isSubQuery =
     (!isEntity && typeof entityOrProperty === "function") ||
     (typeof entityOrProperty === "string" &&
-        entityOrProperty.substr(0, 1) === "(" &&
-        entityOrProperty.substr(-1) === ")")
+        entityOrProperty.charAt(0) === "(" &&
+        entityOrProperty.charAt(entityOrProperty.length - 1) === ")")
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies the use of the deprecated substr() method and proposes a valid replacement with charAt(), which improves code maintainability and aligns with modern JavaScript standards.

Low

@xdc0
Copy link
Author

xdc0 commented Dec 9, 2025

PR Code Suggestions ✨

Category **Suggestion                                                                                                                                    ** Impact
General
Replace deprecated substr method
Replace the deprecated substr() method with charAt() for better code maintainability and future compatibility.

src/query-builder/SelectQueryBuilder.ts [2099-2104]

 const isEntity = this.connection.hasMetadata(entityOrProperty)
 const isSubQuery =
     (!isEntity && typeof entityOrProperty === "function") ||
     (typeof entityOrProperty === "string" &&
-        entityOrProperty.substr(0, 1) === "(" &&
-        entityOrProperty.substr(-1) === ")")
+        entityOrProperty.charAt(0) === "(" &&
+        entityOrProperty.charAt(entityOrProperty.length - 1) === ")")
  • Apply / Chat

Suggestion importance[1-10]: 4
__

Why: The suggestion correctly identifies the use of the deprecated substr() method and proposes a valid replacement with charAt(), which improves code maintainability and aligns with modern JavaScript standards.

Low

  • More

This is not new code added in this PR. I'm merely moving up that evaluation without touching any of the current implementation

@xdc0
Copy link
Author

xdc0 commented Dec 9, 2025

@gioboa Done updating the branch one more time.

The conflict happened due a file name change. No actual code conflict.

@gioboa gioboa requested review from G0maa and pkuczynski December 9, 2025 20:12
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 9, 2025

commit: 3e73121

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 to me. Few small code changes suggestions...

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.

is this PR still valid? there are few feedback to address.

@xdc0
Copy link
Author

xdc0 commented Jan 9, 2026

is this PR still valid? there are few feedback to address.

The PR is still as valid as it was almost 2 years ago. I will address feedback today and send it again for review.

@xdc0 xdc0 force-pushed the fix-map-as-entity branch from d4e7c3d to 3e73121 Compare January 9, 2026 20:29
@G0maa G0maa requested review from gioboa and pkuczynski January 10, 2026 15:47
Copy link
Collaborator

@G0maa G0maa left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution. Will mention other maintainers internally so we could get this merged, please do resolve comments if you've fixed them.

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.

LGTM, thank you @xdc0

@gioboa gioboa dismissed pkuczynski’s stale review January 17, 2026 13:48

The author addressed the suggestions.

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.

This PR is ready to be merged but it's not aligned with the current master branch.
@xdc0 Can you align it please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug comp: query builder size-s Simple tasks that require minimal effort. Estimated effort: 1-2 hours.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

leftJoinAndMapOne does not add target property to entity

10 participants