Skip to content

TreeRepository not loading relations on findDescendants() method using QUERY method (relationLoadStrategy) #9673

Closed
@JoseCToscano

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: SELECTtreeEntity.idAStreeEntity_id, treeEntity.deletedAtAStreeEntity_deletedAt, treeEntity.nameAStreeEntity_name, treeEntity.valueAStreeEntity_value, treeEntity.value_typeAStreeEntity_value_type, treeEntity.typeAStreeEntity_type, treeEntity.parentIdAStreeEntity_parentId, treeEntity.priorityAStreeEntity_priority, treeEntity.mpathAStreeEntity_mpath, treeEntity__rules.idAStreeEntity__rules_id, treeEntity__rules.deletedAtAStreeEntity__rules_deletedAt, treeEntity__rules.factIdAStreeEntity__rules_factId, treeEntity__rules.actionAStreeEntity__rules_action, treeEntity__rules.valueAStreeEntity__rules_value, treeEntity__rules.nodeIdAStreeEntity__rules_nodeId, treeEntity__rules__fact.idAStreeEntity__rules__fact_id, treeEntity__rules__fact.deletedAtAStreeEntity__rules__fact_deletedAt, treeEntity__rules__fact.nameAStreeEntity__rules__fact_name, treeEntity__rules__fact.keyAStreeEntity__rules__fact_key, treeEntity__rules__fact.hintAStreeEntity__rules__fact_hint, treeEntity__rules__fact.isActiveAStreeEntity__rules__fact_isActive, treeEntity__rules__fact.typeAStreeEntity__rules__fact_type, treeEntity__rules__fact.treeIdAStreeEntity__rules__fact_treeId, treeEntity__rules_fact.idAStreeEntity__rules_fact_id, treeEntity__rules_fact.deletedAtAStreeEntity__rules_fact_deletedAt, treeEntity__rules_fact.nameAStreeEntity__rules_fact_name, treeEntity__rules_fact.keyAStreeEntity__rules_fact_key, treeEntity__rules_fact.hintAStreeEntity__rules_fact_hint, treeEntity__rules_fact.isActiveAStreeEntity__rules_fact_isActive, treeEntity__rules_fact.typeAStreeEntity__rules_fact_type, treeEntity__rules_fact.treeIdAStreeEntity__rules_fact_treeId, treeEntity__evaluationNodes.idAStreeEntity__evaluationNodes_id, treeEntity__evaluationNodes.deletedAtAStreeEntity__evaluationNodes_deletedAt, treeEntity__evaluationNodes.processedAtAStreeEntity__evaluationNodes_processedAt, treeEntity__evaluationNodes.resultAStreeEntity__evaluationNodes_result, treeEntity__evaluationNodes.orderAStreeEntity__evaluationNodes_order, treeEntity__evaluationNodes.evaluationTypeAStreeEntity__evaluationNodes_evaluationType, treeEntity__evaluationNodes.nodeIdAStreeEntity__evaluationNodes_nodeId, treeEntity__evaluationNodes.evaluationIdAStreeEntity__evaluationNodes_evaluationId, treeEntity__evaluationNodes__evaluation.idAStreeEntity__evaluationNodes__evaluation_id, treeEntity__evaluationNodes__evaluation.deletedAtAStreeEntity__evaluationNodes__evaluation_deletedAt, treeEntity__evaluationNodes__evaluation.processedAtAStreeEntity__evaluationNodes__evaluation_processedAt, treeEntity__evaluationNodes__evaluation.simulatedAStreeEntity__evaluationNodes__evaluation_simulated, treeEntity__evaluationNodes__evaluation.payloadAStreeEntity__evaluationNodes__evaluation_payload, treeEntity__evaluationNodes__evaluation.resultAStreeEntity__evaluationNodes__evaluation_result, treeEntity__evaluationNodes__evaluation.referenceIdAStreeEntity__evaluationNodes__evaluation_referenceId, treeEntity__evaluationNodes__evaluation.referenceEntityAStreeEntity__evaluationNodes__evaluation_referenceEntity, treeEntity__evaluationNodes__evaluation.versionIdAStreeEntity__evaluationNodes__evaluation_versionIdFROMnode treeEntityLEFT JOINrule treeEntity__rulesONtreeEntity__rules.nodeId=treeEntity.id AND (treeEntity__rules.deletedAtIS NULL) LEFT JOINfact treeEntity__rules__factONtreeEntity__rules__fact.id=treeEntity__rules.factId AND (treeEntity__rules__fact.deletedAtIS NULL) LEFT JOINfact treeEntity__rules_factONtreeEntity__rules_fact.id=treeEntity__rules.factId AND (treeEntity__rules_fact.deletedAtIS NULL) LEFT JOINevaluationNode treeEntity__evaluationNodesONtreeEntity__evaluationNodes.nodeId=treeEntity.id AND (treeEntity__evaluationNodes.deletedAtIS NULL) LEFT JOINevaluation treeEntity__evaluationNodes__evaluationONtreeEntity__evaluationNodes__evaluation.id=treeEntity__evaluationNodes.evaluationId AND (treeEntity__evaluationNodes__evaluation.deletedAtIS NULL) WHERE (treeEntity.mpathLIKE CONCAT((SELECTNode.mpathASpathFROMnode 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.

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions