uptrace / bun

SQL-first Golang ORM
https://bun.uptrace.dev
BSD 2-Clause "Simplified" License
3.65k stars 221 forks source link

Has many self relationship throwing error #884

Open garthreckers opened 1 year ago

garthreckers commented 1 year ago

This may be a similar to this issue but when I try to do a has-many relationship that references the same model, it won't work if the parent field is a pointer (edit: used to say non-pointer but should have said pointer).

This is the non-working code:

type (
    Category struct {
        bun.BaseModel `bun:"category,alias:category"`

        ID          uint        `bun:"id,pk,autoincrement" json:"id"`
        Name        string      `bun:"name" json:"name"`
        ParentID    *uint       `bun:"parent" json:"parent_id"`
        CreatedAt   *time.Time  `bun:"created_at" json:"created_at"`
        UpdatedAt   *time.Time  `bun:"updated_at" json:"updated_at"`
        AllChildren []*Category `bun:"rel:has-many,join:id=parent" json:"all_children,omitempty"`
    }
)

func ListCategories() []Category {
    var categories []Category

    db.GetDB().
        NewSelect().
        Model(&categories).
        Relation("AllChildren").
        Where("parent IS NULL").
        Order("name ASC").
        Scan(context.TODO())

    return categories
}

The above code will return the parent categories without the children. If I check the logs, I see this error:

SELECT `category`.`id`, `category`.`name`, `category`.`parent`, `category`.`created_at`, `category`.`updated_at` FROM `category` WHERE (`category`.`parent` IN (4, 7, 15, 1))      *errors.errorString: bun: has-many relation=AllChildren does not have base model=Category with id=[%!q(*uint=0x400035ab58)] (check join conditions)

If I replace the ParentID with a non-pointer uint and run the code, it works as expected with the parents at the top level and the nested AllChildren.

type (
    Category struct {
        bun.BaseModel `bun:"category,alias:category"`

        ID          uint        `bun:"id,pk,autoincrement" json:"id"`
        Name        string      `bun:"name" json:"name"`
        ParentID    uint        `bun:"parent" json:"parent_id"`
        CreatedAt   *time.Time  `bun:"created_at" json:"created_at"`
        UpdatedAt   *time.Time  `bun:"updated_at" json:"updated_at"`
        AllChildren []*Category `bun:"rel:has-many,join:id=parent" json:"all_children,omitempty"`
    }
)

func ListCategories() []Category {
    var categories []Category

    db.GetDB().
        NewSelect().
        Model(&categories).
        Relation("AllChildren").
        Where("parent IS NULL").
        Order("name ASC").
        Scan(context.TODO())

    return categories
}

Am I missing something?

I tried the duplicate struct that the above issue (they did Message and MessageX) and could get it to work that way but that's not really ideal.

garthreckers commented 1 year ago

@codeliger - Doesn't look like its necessary for a relationship. I have a lot of other working relationships without the leading comma and the documentation doesn't include it: https://bun.uptrace.dev/guide/relations.html#has-many-relation

Thanks for the suggestion though.

codeliger commented 1 year ago

Have you tried renaming AllChildren to Categories, or specifying a table name in the definition?

I think the reason there is no table name defined is because it derives it from Profiles []*Profile `bun:"rel:has-many,join:id=user_id"

garthreckers commented 1 year ago

@codeliger - I tried changing it to Categories but it throws the same error:

*errors.errorString: bun: has-many relation=Categories does not have base model=Category with id=[%!q(*uint=0x40004397c0)] (check join conditions)

When you say specify a table name, where would that be added? I have a table definition in the BaseModel.

From my debugging, it makes the correct database query since I can see the relationship query when I run it in debug mode:

SELECT `category`.`id`, `category`.`name`, `category`.`parent`, `category`.`created_at`, `category`.`updated_at` FROM `category` WHERE (`category`.`parent` IN (1, 2)) 

The issue I believe is when it's trying to attach the relationship query result to the base (parent) query result struct, it's not handling the pointer properly. I'm not sure if doing a comparison between a memory address and the value or there is some other issue somewhere where it should get de-referenced but its not.

taoberquer commented 5 months ago

Has anyone found a solution? I have the same problem :/