Search code examples
javascripttypescripttypeorm

Unable to make tree with closure-table in typeorm


I'm currently working on a project where I need a tree in my database. To me, closure-table is the best bet because I will, at some point need multiple roots in the graph.

I am using typeorm 0.2.45 since nestJs doesn't support 0.3.* yet.

When adding entities to the database, without linkage, no problem:

// ./entity/Node.ts
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, Tree, PrimaryColumn, TreeParent, TreeChildren } from "typeorm"

@Entity()
@Tree("closure-table")
export class Node extends BaseEntity {

    @PrimaryColumn()
    name: string

    @TreeParent()
    parent: Node

    @TreeChildren()
    children: Node[]

}
import { AppDataSource } from "./data-source"
import { Node } from "./entity/Node"

AppDataSource.connect().then(async (conn) => {
    const rNode = conn.getTreeRepository(Node)

    await rNode.createQueryBuilder().delete().execute()

    const [root, child] = await Promise.all(['root', 'child'].map(async (name) => {
        await rNode.insert({name})
        return rNode.findOne(name)
    }))

    console.log(await rNode.findTrees())
}).catch(error => console.log(error))

yields

[
  Node { name: 'root', children: [] },
  Node { name: 'child', children: [] }
]

But as soon as I try to add relations within the tree, all goes bad..

import { AppDataSource } from "./data-source"
import { Node } from "./entity/Node"

AppDataSource.connect().then(async (conn) => {
    const rNode = conn.getTreeRepository(Node)

    await rNode.createQueryBuilder().delete().execute()

    const [root, child] = await Promise.all(['root', 'child'].map(async (name) => {
        await rNode.insert({name})
        return rNode.findOne(name)
    }))

    child.parent = root;
    await rNode.save(child);

    console.log(await rNode.findTrees())
}).catch(error => console.log(error))

Yields

[ Node { name: 'root', children: [] } ]

And the table node_closure is completely empty (note that when filling it properly I do have the tree appearing).

What have I done wrong ?

Thanks


Solution

  • Found the answer, in fact it seems that using repository.insert does not properly updates the closure table (supposed to contain identity relation).

    Since identity does not exist, it cannot be propagated when changing parent.

    Corrected code is

    import { AppDataSource } from "./data-source"
    import { Node } from "./entity/Node"
    
    AppDataSource.connect().then(async (conn) => {
        const rNode = conn.getTreeRepository(Node)
    
        await rNode.createQueryBuilder().delete().execute()
    
        const [root, child] = await Promise.all(['root', 'child'].map(async (name) => {
            await rNode.save({name})
            return rNode.findOne(name)
        }))
    
        child.parent = root;
        await rNode.save(child);
    
        console.log(await rNode.findTrees())
    }).catch(error => console.log(error))
    

    Be careful though, since save is an equivalent of upsert and can and will update an entity if an entity with same primary key is found.