TreeRepository not loading relations on findDescendants()
method using QUERY method (relationLoadStrategy) #9673
Description
Issue description
Fetching relations on TreeRepository's method findDescendants()
uses JOIN strategy instead of QUERY strategy to load relations, even if DataSource's relationLoadStrategy is set to 'query'
Expected Behavior
This is a custom implementation where fetching a TreeRepository with nested relations is required. The expected query built from this method should be a series of different queries to gather all the required rows
Actual Behavior
The raw query results in a complex query with multiple LEFT JOINS to fetch all the corresponding relations. This is causing a memory heap allocation error on my application.
This is the resulting query:
web: query: SELECT
treeEntity.
idAS
treeEntity_id,
treeEntity.
deletedAtAS
treeEntity_deletedAt,
treeEntity.
nameAS
treeEntity_name,
treeEntity.
valueAS
treeEntity_value,
treeEntity.
value_typeAS
treeEntity_value_type,
treeEntity.
typeAS
treeEntity_type,
treeEntity.
parentIdAS
treeEntity_parentId,
treeEntity.
priorityAS
treeEntity_priority,
treeEntity.
mpathAS
treeEntity_mpath,
treeEntity__rules.
idAS
treeEntity__rules_id,
treeEntity__rules.
deletedAtAS
treeEntity__rules_deletedAt,
treeEntity__rules.
factIdAS
treeEntity__rules_factId,
treeEntity__rules.
actionAS
treeEntity__rules_action,
treeEntity__rules.
valueAS
treeEntity__rules_value,
treeEntity__rules.
nodeIdAS
treeEntity__rules_nodeId,
treeEntity__rules__fact.
idAS
treeEntity__rules__fact_id,
treeEntity__rules__fact.
deletedAtAS
treeEntity__rules__fact_deletedAt,
treeEntity__rules__fact.
nameAS
treeEntity__rules__fact_name,
treeEntity__rules__fact.
keyAS
treeEntity__rules__fact_key,
treeEntity__rules__fact.
hintAS
treeEntity__rules__fact_hint,
treeEntity__rules__fact.
isActiveAS
treeEntity__rules__fact_isActive,
treeEntity__rules__fact.
typeAS
treeEntity__rules__fact_type,
treeEntity__rules__fact.
treeIdAS
treeEntity__rules__fact_treeId,
treeEntity__rules_fact.
idAS
treeEntity__rules_fact_id,
treeEntity__rules_fact.
deletedAtAS
treeEntity__rules_fact_deletedAt,
treeEntity__rules_fact.
nameAS
treeEntity__rules_fact_name,
treeEntity__rules_fact.
keyAS
treeEntity__rules_fact_key,
treeEntity__rules_fact.
hintAS
treeEntity__rules_fact_hint,
treeEntity__rules_fact.
isActiveAS
treeEntity__rules_fact_isActive,
treeEntity__rules_fact.
typeAS
treeEntity__rules_fact_type,
treeEntity__rules_fact.
treeIdAS
treeEntity__rules_fact_treeId,
treeEntity__evaluationNodes.
idAS
treeEntity__evaluationNodes_id,
treeEntity__evaluationNodes.
deletedAtAS
treeEntity__evaluationNodes_deletedAt,
treeEntity__evaluationNodes.
processedAtAS
treeEntity__evaluationNodes_processedAt,
treeEntity__evaluationNodes.
resultAS
treeEntity__evaluationNodes_result,
treeEntity__evaluationNodes.
orderAS
treeEntity__evaluationNodes_order,
treeEntity__evaluationNodes.
evaluationTypeAS
treeEntity__evaluationNodes_evaluationType,
treeEntity__evaluationNodes.
nodeIdAS
treeEntity__evaluationNodes_nodeId,
treeEntity__evaluationNodes.
evaluationIdAS
treeEntity__evaluationNodes_evaluationId,
treeEntity__evaluationNodes__evaluation.
idAS
treeEntity__evaluationNodes__evaluation_id,
treeEntity__evaluationNodes__evaluation.
deletedAtAS
treeEntity__evaluationNodes__evaluation_deletedAt,
treeEntity__evaluationNodes__evaluation.
processedAtAS
treeEntity__evaluationNodes__evaluation_processedAt,
treeEntity__evaluationNodes__evaluation.
simulatedAS
treeEntity__evaluationNodes__evaluation_simulated,
treeEntity__evaluationNodes__evaluation.
payloadAS
treeEntity__evaluationNodes__evaluation_payload,
treeEntity__evaluationNodes__evaluation.
resultAS
treeEntity__evaluationNodes__evaluation_result,
treeEntity__evaluationNodes__evaluation.
referenceIdAS
treeEntity__evaluationNodes__evaluation_referenceId,
treeEntity__evaluationNodes__evaluation.
referenceEntityAS
treeEntity__evaluationNodes__evaluation_referenceEntity,
treeEntity__evaluationNodes__evaluation.
versionIdAS
treeEntity__evaluationNodes__evaluation_versionIdFROM
node
treeEntityLEFT JOIN
rule
treeEntity__rulesON
treeEntity__rules.
nodeId=
treeEntity.
id AND (
treeEntity__rules.
deletedAtIS NULL) LEFT JOIN
fact
treeEntity__rules__factON
treeEntity__rules__fact.
id=
treeEntity__rules.
factId AND (
treeEntity__rules__fact.
deletedAtIS NULL) LEFT JOIN
fact
treeEntity__rules_factON
treeEntity__rules_fact.
id=
treeEntity__rules.
factId AND (
treeEntity__rules_fact.
deletedAtIS NULL) LEFT JOIN
evaluationNode
treeEntity__evaluationNodesON
treeEntity__evaluationNodes.
nodeId=
treeEntity.
id AND (
treeEntity__evaluationNodes.
deletedAtIS NULL) LEFT JOIN
evaluation
treeEntity__evaluationNodes__evaluationON
treeEntity__evaluationNodes__evaluation.
id=
treeEntity__evaluationNodes.
evaluationId AND (
treeEntity__evaluationNodes__evaluation.
deletedAtIS NULL) WHERE (
treeEntity.
mpathLIKE CONCAT((SELECT
Node.
mpathAS
pathFROM
node
NodeWHERE (
Node.
idIN (?) ) AND (
Node.
deletedAtIS NULL )), '%') ) AND (
treeEntity.
deletedAtIS NULL ) -- PARAMETERS: [19805]
Steps to reproduce
This is the implementation causing the problem.
let nodes = await this.dataSource.manager .getTreeRepository(Node) .findDescendants(node, { relations: [ 'rules', 'rules.fact', 'evaluationNodes', 'evaluationNodes.evaluation', ], });
Here are the entities involved (I'm just providing relevant cols)
`
@entity('rule')
export class Rule extends DefaultEntity {
@manytoone(() => Fact, (fact) => fact.rules, { eager: true })
fact?: Fact;
@Column()
factId?: number;
@ManyToOne(() => Node, (node) => node.rules)
node?: Node;
// Node.entity.ts
@entity('node')
@TreeDecorator('materialized-path')
export class Node extends DefaultEntity {
@TreeChildren({ cascade: true })
children?: Node[];
@Column()
parentId?: number;
@TreeParent()
parent?: Node;
@OneToMany(() => Rule, (rule) => rule.node, {
cascade: true,
onDelete: 'CASCADE',
})
rules?: Rule[];
@OneToMany(() => EvaluationNode, (evaluation) => evaluation.node)
evaluationNodes?: EvaluationNode[];
@OneToOne(() => Version, (version) => version.tree)
version?: Version;
}
`
`
@entity('evaluationNode')
export class EvaluationNode extends DefaultEntity {
@Column()
nodeId!: number;
@ManyToOne(() => Node, (node) => node.evaluationNodes)
node?: Node;
// PARENT
@ManyToOne(() => Evaluation, (evaluation) => evaluation.evaluationNodes)
evaluation?: Evaluation;
}
`
My Environment
Dependency | Version |
---|---|
Operating System | macOS BigSur 11.3.1 |
Node.js version | v14.18.0 |
Typescript version | 4.5.5 |
TypeORM version | 0.3.11 |
Additional Context
I know that a similar situation was addressed and resolved regarding the relationLoadStrategy for regular repositories. Maybe this change needs to be replicated into the TreeRepository? I could help writting the PR, but I just need a little insight on where to start
Relevant Database Driver(s)
- aurora-mysql
- aurora-postgres
- better-sqlite3
- cockroachdb
- cordova
- expo
- mongodb
- mysql
- nativescript
- oracle
- postgres
- react-native
- sap
- spanner
- sqlite
- sqlite-abstract
- sqljs
- sqlserver
Are you willing to resolve this issue by submitting a Pull Request?
Yes, I have the time, but I don't know how to start. I would need guidance.