bun
bun copied to clipboard
Error `has-many relation ... check join conditions` on select with relation via *string
Error: *errors.errorString: bun: has-many relation=Books does not have base model=User with id=[%!q(*string=0xc00019efe0)] (check join conditions)
Works fine in go-pg.
If UserID *string
is replaced with any not nullable type, it works fine.
Test err example:
package main
import (
"context"
"database/sql"
"fmt"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/sqlitedialect"
"github.com/uptrace/bun/driver/sqliteshim"
"github.com/uptrace/bun/extra/bundebug"
)
type User struct {
ID string `bun:",pk"`
Books []*Book `bun:"rel:has-many,join:id=user_id"`
}
type Book struct {
ID int64 `bun:",pk,autoincrement"`
UserID *string `bun:"type:varchar(64)"` // No err if UserID is `string`
}
func main() {
ctx := context.Background()
sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:?cache=shared")
if err != nil {
panic(err)
}
fmt.Println("Setting up DB connection...")
db := bun.NewDB(sqldb, sqlitedialect.New())
defer db.Close()
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
fmt.Println("Creating tables...")
if err := createSchema(ctx, db); err != nil {
panic(err)
}
fmt.Println("Getting object from DB:")
user := new(User)
if err := db.NewSelect().
Model(user).
Column("user.*").
Relation("Books").
OrderExpr("user.id ASC").
Limit(1).
Scan(ctx); err != nil {
panic(err)
}
fmt.Println(user.ID, user.Books[0], user.Books[1])
fmt.Println("Test completed successfully.")
}
func createSchema(ctx context.Context, db *bun.DB) error {
_, err := db.Exec(`
CREATE TABLE users
(
id varchar(64) PRIMARY KEY
);
CREATE TABLE books
(
id int PRIMARY KEY,
user_id varchar(64)
);
`)
if err != nil {
return fmt.Errorf("failed to create tables: %w", err)
}
userIDA := "1"
userIDB := "2"
testUsers := []*User{
{ID: userIDA},
{ID: userIDB},
}
if _, err := db.NewInsert().Model(&testUsers).Exec(ctx); err != nil {
return err
}
book := []*Book{
{ID: 1, UserID: &userIDA},
{ID: 2, UserID: &userIDA},
{ID: 3, UserID: &userIDB},
}
if _, err := db.NewInsert().Model(&book).Exec(ctx); err != nil {
return err
}
return nil
}
@evt can you or anyone help me take a look real quick?
type Event struct { bun.BaseModel
bun:"table:events"`
Id string `bun:"id,pk,notnull,unique"`
UserId string `bun:"user_id,notnull"`
User *User `bun:"rel:belongs-to,join:user_id=id"`
}
type User struct {
bun.BaseModel bun:"table:users"
Id string `bun:"id,pk,notnull,unique"`
Username string `bun:"username,notnull,nullzero,unique"`
}`
But the join operation keeps returning
`ERROR: column reference "id" is ambiguous (SQLSTATE=42702)`
I've been stressing about it for a while.
I have the same problem but with *int. Would expect it work so you can have optional/nullable foreign keys or should this be done in another way?
I have found a fix for this. The issue wasnt with the model, but with the way we are calling the relations
I initially had something like
err := da.db.NewSelect().Model(&conversations).Relation("LastMessage").Relation("Member").Relation("Creator").WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery { return q.Where("member_id = ?", req.UserId).WhereOr("creator_id = ?", req.UserId) }).
This kept failing until I switched to using dot notation in the Where
queries
err := da.db.NewSelect().Model(&conversations).Relation("LastMessage").Relation("Member").Relation("Creator").WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery { return q.Where("conversation.member_id = ?", req.UserId).WhereOr("conversation.creator_id = ?", req.UserId) }).
We have the same problem with sql.Null*
types.
This is the code that make it work in go-pg :
https://github.com/go-pg/pg/blob/v10.10.7/orm/model_table_many.go#L50-L51
There is a map from foreign key values to loaded children models. In go-pg, this map is named manyModel.dstValues
and has the type map[string][]reflect.Value
. When the map is written and read, we always "dereference" values. So for example we can set keys of type int64
, and query with keys of type *int64
, and we can find the children models that were loaded.
This is the same code that doesn't work in bun :
https://github.com/uptrace/bun/blob/v1.1.8/model_table_has_many.go#L106
The structure is similar, but the map is named hasManyModel.baseValues
and the type is map[internal.MapKey][]reflect.Value
, with
type MapKey struct {
iface interface{}
}
The problem is that the map key is no longer a string, but it is an internal type that is related to types of foreign keys. If the map is set with keys of type int64
, we can no longer query it with keys of type *int64
.
Questions for @vmihailenco :
- What is the reasoning behind this change between go-pg and bun ?
- Would you accept a PR reverting back bun to the go-pg behavior ?
(We need this issue to be fixed to consider bun for our project.)
Thanks.
I got two models:
type Region struct {
bun.BaseModel `bun:"table:regions"`
ID *uuid.UUID `json:"id" bun:"id,pk,type:uuid,default:gen_random_uuid()"`
Organizations []*Organization `bun:"rel:has-many,join:id=region_id"`
}
type Organization struct {
bun.BaseModel `bun:"table:organizations"`
ID *uint64 `json:"id" bun:"id,pk,autoincrement"`
RegionID *uuid.UUID `json:"region_id" bun:"region_id"`
}
I use them like this:
var items []Region
err := db.NewSelect().Model(&items).Relation("Organizations").Scan(ctx)
And gain this error:
bun: has-many relation=Organizations does not have base model=Region with id=["7436cc71-2b6d-4eb9-a04e-b7b2f45a0db5"] (check join conditions)
But if I make Region.ID and Organization.RegionID non-pointers - everything works fine. I consider there is something wrong with reflection in https://github.com/uptrace/bun/blob/master/model_table_has_many.go
Exact same issue for me.
I join on a *string
rather than string
, and it errors. I cannot use a string
here, so I have to figure out a workaround.
type Individual struct {
bun.BaseModel `bun:"individuals,alias:ind"`
ID string `json:"ID" required:"true" bun:",pk"`
WorkspaceID string `json:"WorkspaceID" required:"true"`
HubspotObjects []*HubspotObject `json:"HubspotObjects" bun:"rel:has-many,join:id=individual_id"`
}
type HubspotObject struct {
bun.BaseModel `bun:"hubspot_objects,alias:hso"`
ID string `json:"ID" required:"true" bun:",pk"`
WorkspaceID string `json:"WorkspaceID" required:"true"`
IndividualID *string `json:"IndividualID"` // can be null
OrganizationID *string `json:"OrganizationID"` // can be null
SyncedAt *time.Time `json:"SyncedAt"`
UpdatedAt time.Time `json:"UpdatedAt" required:"true"`
CreatedAt time.Time `json:"CreatedAt" required:"true"`
}
func (h *Handlers) SearchIndividuals() {
ids := []string{"a", "z"}
// load the individuals
individuals := []entity.Individual{}
if err := h.bun.NewSelect().
Model(&individuals).
Relation("HubspotObjects").
Where("ind.id IN (?)", bun.In(ids)).
Scan(ctx); err != nil {
return nil, apperrors.SqlError(err, "search individuals")
}
}
And it errors: errors.errorString: bun: has-many relation=HubspotObjects does not have base model=Individual with id=[%!q(*string=0x1400003d2b0)] (check join conditions)
@vmihailenco any thoughts on how we can make it work?