bun icon indicating copy to clipboard operation
bun copied to clipboard

Error `bun: TableName does not have column=ColumnName"` on relations nested inside a has-many relation

Open enzalito opened this issue 1 year ago • 1 comments

I found a bug that seems to happen with queries that include a has-many relation and nested relations that refer to the same table (not very clear I know, refer to the reproducer bellow). It took me some time to pinpoint the cause of the problem but I eventually found it. I will attach a PR to this issue with the fix I came up with.

package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"

	embeddedpostgres "github.com/fergusstrange/embedded-postgres"
	"github.com/uptrace/bun"
	"github.com/uptrace/bun/dialect/pgdialect"
	"github.com/uptrace/bun/driver/pgdriver"
)

type Foo struct {
	Id   int64 `bun:",pk,autoincrement,nullzero"`
	Name string
	Bars []*Bar `bun:"rel:has-many,join:id=foo_id"`
}

type Bar struct {
	Id    int64 `bun:",pk,nullzero"`
	Name  string
	FooId int64 `bun:",nullzero"`
	BazId int64 `bun:",nullzero"`
	Baz   *Baz  `bun:"rel:belongs-to,join:baz_id=id"`
	Quux  *Quux `bun:"rel:has-one,join:id=id"`
}

type Baz struct {
	Id    int64 `bun:",pk,nullzero"`
	Name  string
	QuxId int64 `bun:",nullzero"`
	Qux   *Qux  `bun:"rel:belongs-to,join:qux_id=id"`
	Quux  *Quux `bun:"rel:has-one,join:id=id"`
}

type Qux struct {
	Id   int64 `bun:",pk,nullzero"`
	Name string
	Quux *Quux `bun:"rel:has-one,join:id=id"`
}

type Quux struct {
	Id   int64 `bun:",pk,autoincrement,nullzero"`
	Name string
}

func main() {
	pgServer := embeddedpostgres.NewDatabase(
		embeddedpostgres.DefaultConfig().Logger(nil),
	)
	err := pgServer.Start()
	if err != nil {
		panic(err)
	}
	defer pgServer.Stop()

	ctx := context.Background()
	dsn := "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"
	sqlDb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
	db := bun.NewDB(sqlDb, pgdialect.New())

	_, err = db.NewCreateTable().
		Model((*Foo)(nil)).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewCreateTable().
		Model((*Bar)(nil)).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewCreateTable().
		Model((*Baz)(nil)).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewCreateTable().
		Model((*Quux)(nil)).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewCreateTable().
		Model((*Qux)(nil)).
		Exec(ctx)
	if err != nil {
		panic(err)
	}

	_, err = db.NewInsert().
		Model(&[]*Quux{
			{Name: "quux_qux"},
		}).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewInsert().
		Model(&[]*Quux{
			{Name: "quux_baz"},
		}).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewInsert().
		Model(&[]*Quux{
			{Name: "quux_bar"},
		}).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewInsert().
		Model(&[]*Qux{
			{Name: "qux", Id: 1},
		}).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewInsert().
		Model(&[]*Baz{
			{Name: "baz", Id: 2, QuxId: 1},
		}).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewInsert().
		Model(&[]*Bar{
			{Name: "bar", Id: 3, FooId: 1, BazId: 2},
		}).
		Exec(ctx)
	if err != nil {
		panic(err)
	}
	_, err = db.NewInsert().
		Model(&[]*Foo{
			{Name: "foo"},
		}).
		Exec(ctx)
	if err != nil {
		panic(err)
	}

	res := Foo{}
	err = db.NewSelect().
		Model(&res).
		Where("id = 1").
		Relation("Bars").
		Relation("Bars.Quux").
		Relation("Bars.Baz").
		Relation("Bars.Baz.Quux").
		Relation("Bars.Baz.Qux").
		Relation("Bars.Baz.Qux.Quux").
		Scan(ctx)
	if err != nil {
		panic(err)
	}
	resJson, _ := json.MarshalIndent(res, "", "  ")
	fmt.Println(string(resJson))
}

enzalito avatar Jun 05 '23 15:06 enzalito

Seems like I stumbled across the same issue. My scenario is rather simple: A is my reference point and I'm joining B, C and finally D. Somehow only C gets an error on the last field. The revert of #850 seems to trade in quality for memory, doesn't it? Is there any plan to fix this again?

steve-hb avatar Feb 01 '24 22:02 steve-hb