Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nullzero relations not being fetched #955

Open
kubakl opened this issue Feb 6, 2024 · 11 comments
Open

nullzero relations not being fetched #955

kubakl opened this issue Feb 6, 2024 · 11 comments
Assignees

Comments

@kubakl
Copy link

kubakl commented Feb 6, 2024

When defining a nullzero relation it fails when using Scan().
Example:

type MainTable struct {
    ID int64 `bun:",pk,notnull`
    ChildID int64
    Child *Parent `bun:",rel:has-one,join:child_id=id"`
}

type Parent struct {
    ID int64 `bun:",pk,notnull"`
    ChildID int64 `bun:",nullzero"`
    Child *Child `bun:",rel:has-one,join:child_id=id"`
}

type Child struct {
    ID int64
    ...
}

With this structure when I try to fetch MainTable and add a relation using Relation("Child.Child") it rather returns no element even though it exists and can be fetched directly or panics with:

reflect: call of reflect.Value.Field on ptr Value

I found a similar issue here, not sure if it's related #799

@codeliger
Copy link
Contributor

#943

If i understand correctly i have the same issue here.

@JunNishimura
Copy link
Contributor

@kubakl

I have implemented it as you have shown as an example in the following way, but I could not reproduce the bug.

type Child struct {
	ID int `bun:"id,pk,notnull"`
}

type Parent struct {
	ID      int `bun:"id,pk,notnull"`
	ChildID int
	Child   *Child `bun:"rel:has-one,join:child_id=id"`
}

type Main struct {
	ID      int `bun:"id,pk,notnull"`
	ChildID int
	Child   *Parent `bun:"rel:has-one,join:child_id=id"`
}
func main() {
	...

	db := bun.NewDB(sqlDB, mysqldialect.New())

	db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))

	ctx := context.Background()

	// Create tables
	if _, err := db.NewDropTable().Model((*Main)(nil)).IfExists().Exec(ctx); err != nil {
		log.Fatalf("create table failed: %v", err)
	}
	if _, err := db.NewDropTable().Model((*Parent)(nil)).IfExists().Exec(ctx); err != nil {
		log.Fatalf("create table failed: %v", err)
	}
	if _, err := db.NewDropTable().Model((*Child)(nil)).IfExists().Exec(ctx); err != nil {
		log.Fatalf("create table failed: %v", err)
	}
	if _, err := db.NewCreateTable().Model((*Child)(nil)).Exec(ctx); err != nil {
		log.Fatalf("create table failed: %v", err)
	}
	if _, err := db.NewCreateTable().Model((*Parent)(nil)).Exec(ctx); err != nil {
		log.Fatalf("create table failed: %v", err)
	}
	if _, err := db.NewCreateTable().Model((*Main)(nil)).Exec(ctx); err != nil {
		log.Fatalf("create table failed: %v", err)
	}
	// Insert data
	if _, err := db.NewInsert().Model(&Child{ID: 1}).Exec(ctx); err != nil {
		log.Fatalf("insert failed: %v", err)
	}
	if _, err := db.NewInsert().Model(&Parent{ID: 1, ChildID: 1}).Exec(ctx); err != nil {
		log.Fatalf("insert failed: %v", err)
	}
	if _, err := db.NewInsert().Model(&Main{ID: 1, ChildID: 1}).Exec(ctx); err != nil {
		log.Fatalf("insert failed: %v", err)
	}
	// Select data
	var main Main
	if err := db.NewSelect().Model(&main).Relation("Child.Child").Where("main.id = ?", 1).Scan(ctx); err != nil {
		log.Fatalf("select failed: %v", err)
	}
	log.Printf("%+v", main) // result: {ID:1 ChildID:1 Child:0xc00020c2a0}
}

Could you please tell me a bit more about under what conditions the bug occurred (which RDBMS, which version of Bun)?

@kubakl
Copy link
Author

kubakl commented Aug 9, 2024

@JunNishimura I can see you're not checking it like the description specifies this happens when the ChildID in the Parent table has bun:",nullzero" specified. This doesn't work for me, I'm using postgres lib and I'm working on bun version v1.1.14

Copy link

This issue has been automatically marked as stale because it has not had activity in the last 30 days. If there is no update within the next 7 days, this issue will be closed.

@github-actions github-actions bot added the stale label Nov 17, 2024
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Nov 24, 2024
@kubakl
Copy link
Author

kubakl commented Nov 26, 2024

@JunNishimura This is a big issue. From what I understand it's somehow related with this #377. I can see it hasn't been touched either...

@j2gg0s
Copy link
Collaborator

j2gg0s commented Nov 27, 2024

@kubakl

Could you provide an example of the usage that triggers the error? I couldn’t find the relevant code.
The following code runs successfully on both the latest version and v1.1.14.

package main

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

	"github.com/uptrace/bun"
	"github.com/uptrace/bun/dialect/pgdialect"
	"github.com/uptrace/bun/driver/pgdriver"
	"github.com/uptrace/bun/extra/bundebug"
)

func main() {
	ctx := context.Background()

	sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN("postgres://bun:bun@localhost:5433/bun?sslmode=disable")))

	db := bun.NewDB(sqldb, pgdialect.New())
	db.AddQueryHook(bundebug.NewQueryHook(
		bundebug.WithVerbose(true),
		bundebug.FromEnv("BUNDEBUG"),
	))
	defer db.Close()

	if err := db.ResetModel(ctx, (*MainTable)(nil), (*Parent)(nil), (*Child)(nil)); err != nil {
		fmt.Println(err)
	}

	childs := []*Child{
		{ID: 1},
	}
	parents := []Parent{
		{ID: 1, ChildID: 1},
		{ID: 2, ChildID: 1},
		{ID: 3, ChildID: 2},
	}
	mainTables := []MainTable{
		{ID: 1, ChildID: 1},
		{ID: 2, ChildID: 2},
		{ID: 3, ChildID: 3},
	}

	db.NewInsert().Model(&childs).Exec(ctx)
	db.NewInsert().Model(&parents).Exec(ctx)
	db.NewInsert().Model(&mainTables).Exec(ctx)

	// query

	ts := []MainTable{}

	if err := db.NewSelect().
		Model(&ts).
		Relation("Child.Child").
		Scan(ctx); err != nil {
		panic(err)
	}
	fmt.Println(ts[0].ID, ts[0].ChildID)
	fmt.Println(ts[1].ID, ts[1].ChildID)
	fmt.Println(ts[2].ID, ts[2].ChildID)
}

type MainTable struct {
	ID      int64 `bun:",pk,notnull"`
	ChildID int64
	Child   *Parent `bun:",rel:has-one,join:child_id=id"`
}

type Parent struct {
	ID      int64  `bun:",pk,notnull"`
	ChildID int64  `bun:",nullzero"`
	Child   *Child `bun:",rel:has-one,join:child_id=id"`
}

type Child struct {
	ID   int64
	Name string
}

@kubakl
Copy link
Author

kubakl commented Nov 27, 2024

Hi, first of all, thanks for picking up on this topic. I could finally reproduce it, here's the code:

package main

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

	"github.com/uptrace/bun"
	"github.com/uptrace/bun/dialect/pgdialect"
	"github.com/uptrace/bun/driver/pgdriver"
	"github.com/uptrace/bun/extra/bundebug"
)

func main() {
	ctx := context.Background()

	sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN("postgres://user:pass@localhost:5433/test?sslmode=disable")))

	db := bun.NewDB(sqldb, pgdialect.New())
	db.AddQueryHook(bundebug.NewQueryHook(
		bundebug.WithVerbose(true),
		bundebug.FromEnv("BUNDEBUG"),
	))
	defer db.Close()

	if err := db.ResetModel(ctx, (*MainTable)(nil), (*Parent)(nil), (*Child)(nil)); err != nil {
		fmt.Println(err)
	}

	childs := []*Child{{Name: "Test 1"}, {Name: "Test 2"}}
	parents := []Parent{{Child1ID: 1}}
	mainTables := []MainTable{{ParentID: 1, Child2ID: 1}}

	db.NewInsert().Model(&childs).Exec(ctx)
	db.NewInsert().Model(&parents).Exec(ctx)
	db.NewInsert().Model(&mainTables).Exec(ctx)

	// query

	ts := []MainTable{}

	if err := db.NewSelect().
		Model(&ts).
		Relation("Child1").
		Relation("Child2").
		Relation("Parent.Child1").
		Relation("Parent.Child2").
		Scan(ctx); err != nil {
		panic(err)
	}

	defer func() {
		if recover() != nil {
			println("panicked")
		}
	}()
}

type MainTable struct {
	ID       int64   `bun:",pk,unique,autoincrement"`
	ParentID int64   `bun:",nullzero"`
	Parent   *Parent `bun:"par,rel:belongs-to,join:parent_id=id"`
	Child1ID int64   `bun:"child_1_id,nullzero"`
	Child1   *Child  `bun:"ch1,rel:has-one,join:child_1_id=id"`
	Child2ID int64   `bun:"child_2_id,nullzero"`
	Child2   *Child  `bun:"ch2,rel:has-one,join:child_2_id=id"`
}

type Parent struct {
	ID       int64  `bun:",pk,unique,autoincrement"`
	Child1ID int64  `bun:"child_1_id,nullzero"`
	Child1   *Child `bun:"ch1,rel:has-one,join:child_1_id=id"`
	Child2ID int64  `bun:"child_2_id,nullzero"`
	Child2   *Child `bun:"ch2,rel:has-one,join:child_2_id=id"`
}

type Child struct {
	ID   int64 `bun:",pk,unique,autoincrement"`
	Name string
}

@kubakl
Copy link
Author

kubakl commented Nov 27, 2024

Additionally I noticed that if the model is referring to a database view instead of a table and you try to load a nullable relation it will fail with the same error: panic: reflect: call of reflect.Value.Field on ptr Value.

@Tiscs
Copy link
Collaborator

Tiscs commented Nov 27, 2024

Additionally I noticed that if the model is referring to a database view instead of a table and you try to load a nullable relation it will fail with the same error: panic: reflect: call of reflect.Value.Field on ptr Value.

Could you provide the module versions referenced in go.mod? I just tried to reproduce this issue, but the unit test passed (based on v1.2.5 and v1.2.6).

@kubakl
Copy link
Author

kubakl commented Nov 27, 2024

It's based on version v1.1.17

@kubakl
Copy link
Author

kubakl commented Nov 27, 2024

After updating to 1.2.6 this particular example is indeed working. I will update once I can test it in my codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants