I'm trying to Preload data from a One to Many relationship, yet I always get an "ApiKeys: unsupported relations for schema Client" error. (The reason structs are pointers is because I'm using gqlgen and that's the default configuration)
type Client struct {
// Client ID
ID int `json:"id"`
UserName string `json:"userName"`
// Client login hashed password
Password string `json:"password"`
// ApiKeys
APIKeys []*APIKey `json:"apiKeys"`
}
type APIKey struct {
// ApiKey Index
ID int `json:"id"`
// ApiKey Value
Key string `json:"key"`
// ApiKey Client Relation
ClientID int `json:"clientID"`
// ApiKey Client Info
Client *Client `json:"client"`
}
And this is the function that calls the Preload of ApiKeys.
func (r *queryResolver) ClientInfoResolver(username string, password string) (*model.Client, error) {
var clients []*model.Client
var client *model.Client
query := r.Resolver.DB
query = query.Where("user_name = ? AND password = ?", username, password).Preload("ApiKeys").Find(&clients)
if query.Error != nil {
return client, query.Error
}
return clients[0], nil
}
I understand by gorm's documentation that the foreign key for the relation is ClientID, despite not being explicit (doesn't work by specifying it either) am I understanding something wrong here?
You list APIKeys as the struct field name but try and use ApiKeys as the FK.
.Preload("ApiKeys")
// Should be
.Preload("APIKeys")
Or, if you want to use ApiKeys as the foreign key, use a Gorm struct tag to do this.
Full working example
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Client struct {
// ApiKey Index
ID int `json:"id"`
UserName string `json:"userName"`
// Client login hashed password
Password string `json:"password"`
// ApiKeys
APIKeys []*APIKey `json:"apiKeys"`
}
type APIKey struct {
// ApiKey Index
ID int `json:"id"`
// ApiKey Value
Key string `json:"key"`
// ApiKey Client Relation
ClientID int `json:"clientID"`
// ApiKey Client Info
Client *Client `json:"client"`
}
func main() {
db, err := gorm.Open(sqlite.Open("many2many.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
err = db.AutoMigrate(&APIKey{}, &Client{})
if err != nil {
fmt.Print(err)
}
clientOne := Client{
UserName: "Client One",
}
db.Create(&clientOne)
apiKeyOne := APIKey{
Key:"one",
Client: &clientOne,
}
apiKeyTwo := APIKey{
Key:"two",
Client: &clientOne,
}
db.Create(&apiKeyOne)
db.Create(&apiKeyTwo)
// Fetch from DB
fetchedClient := Client{}
db.Debug().Preload("APIKeys").Find(&fetchedClient, clientOne.ID)
fmt.Println(fetchedClient)
db.Delete(&clientOne)
db.Delete(&apiKeyOne)
db.Delete(&apiKeyTwo)
}
Has it been resolved? I also encountered the same problem。
when i trying to Preload data from a One to Many relationship,
report:unsupported relations for schema TeacherInfo
github.com/99designs/gqlgen v0.13.0
gorm.io/gorm v1.21.8
models_gen:
func (TeacherInfo) TableName() string {
return "teacher_info"
}
type TeacherInfo struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name"`
Avatar string `json:"avatar" `
Info string `json:"info" `
Score float64 `json:"score" `
Role int `json:"role" `
TeacherScore []*TeacherScore `json:"teacherScore" gorm:"foreignkey:TID; references:ID"`
}
func (TeacherScore) TableName() string {
return "teacher_score"
}
type TeacherScore struct {
ID int `json:"id" `
TID int `json:"t_id" `
Comment string `json:"comment" `
UID int `json:"u_id" `
Score float64 `json:"score" `
}
resolvers:
func (r *queryResolver) TeacherScore(ctx context.Context, id int) (*model.TeacherInfo, error) {
var teacherInfo model.TeacherInfo
wrong: dao.DB.Debug().Preload("teacher_score").First(&teacherInfo)
right: here is teacherInfo's cloume TeacherScore
dao.DB.Debug().Preload("TeacherScore").First(&teacherInfo)
return &teacherInfo, nil
}
resolved
Related
Is there any way to auto generate UUID with GORM while saving the object in DB in go?
I am having experience with ROR migrations, where ID would be auto generated and PK by default.
Here is my code
Todo.go
package model
type Todo struct {
ID string `json:"id"`
Text string `json:"text"`
Done bool `json:"done"`
}
schema.resolvers.go
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
db, _ := database.connect()
defer db.Close()
todo := &model.Todo{
Text: input.Text,
ID: fmt.Sprintf("%d", rand.Int()), // I don't want to provide ID like this
}
db.Create(&todo)
...
}
models_gen.go
# GraphQL schema example
#
# https://gqlgen.com/getting-started/
type Todo {
id: ID!
text: String!
done: Boolean!
}
input NewTodo {
text: String!
userId: String!
}
type Mutation {
createTodo(input: NewTodo!): Todo!
}
Any help would be really appreciated.
Assuming you're using gorm v2, you can use hooks to achieve this. The documentation about hooks is here
In particular, you can have a look at the BeforeCreate hook. Applied on a model, it provides a container for a function to be run before the creation in the database as its name state.
The GORM documentation also provides an example to generate a UUID when we create a user entity in a database with Gorm, which seems to be what you're looking for:
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if !u.IsValid() {
err = errors.New("can't save invalid data")
}
return
}
GORM Hooks is the most obvious solution to generate UUID when creating an object.
func (t *Todo) BeforeCreate(tx *gorm.DB) (err error) {
t.ID = uuid.New().String()
return
}
But if you want to generate UUID for every object, you could define a base struct and embed it into object structs.
type Base struct {
ID string `json:"id"`
}
func (b *Base) BeforeCreate(tx *gorm.DB) (err error) {
b.ID = uuid.New().String()
return
}
type Todo struct {
Base
Text string `json:"text"`
Done bool `json:"done"`
}
type User struct {
Base
Name string `json:"name"`
}
Or just use the GORM plugin next to generate UUID for objects.
import "github.com/invzhi/next"
plugin := NewPlugin()
// register uuid generate function
plugin.Register("uuid", func(_, zero bool) (interface{}, error) {
if !zero {
return nil, SkipField
}
return uuid.New().String()
})
_ = db.Use(plugin)
type Todo struct {
ID string `gorm:"type:varchar(20);column:id;next:uuid"` // next:uuid meaning generate uuid when create
Text string `gorm:"type:varchar(100);column:text`
Done bool `gorm:"type:boolean;column:done`
}
type User struct {
ID string `gorm:"type:varchar(20);column:id;next:uuid"` // next:uuid meaning generate uuid when create
Name string `gorm:"type:varchar(100);column:name"`
}
I am trying to load one to one and one to many associations for one of my object using GORM. I keep getting the following error when I am trying to run.
{
"errors": [
{
"message": "can't preload field UserProfiles for models.Renter",
"path": [
"users"
]
}
],
"data": null
}
Here is my code
type User struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
Email string `gorm:"not null;unique_index"`
Password string
FirstName *string
LastName *string
Renter Renter `gorm:"auto_preload"`
Rentee Rentee `gorm:"auto_preload"`
UserProfiles []UserProfile `gorm:"association_autocreate:false;association_autoupdate:false"`
Roles []Role `gorm:"many2many:user_roles;association_autocreate:false;association_autoupdate:false"`
Permissions []Permission `gorm:"many2many:user_permissions;association_autocreate:false;association_autoupdate:false"`
}
// UserProfile saves all the related OAuth Profiles
type UserProfile struct {
BaseModelSeq
Email string `gorm:"unique_index:idx_email_provider_external_user_id"`
UserID uuid.UUID `gorm:"not null;index"`
User User `gorm:"association_autocreate:false;association_autoupdate:false"`
Provider string `gorm:"not null;index;unique_index:idx_email_provider_external_user_id;default:'DB'"` // DB means database or no ExternalUserID
ExternalUserID string `gorm:"not null;index;unique_index:idx_email_provider_external_user_id"` // User ID
Name string
FirstName string
LastName string
AvatarURL string `gorm:"size:1024"`
Description string `gorm:"size:1024"`
}
type Renter struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
UserID uuid.UUID `gorm:"unique;not null;index"`
Verified bool
Properties []Property `gorm:"association_autocreate:false;association_autoupdate:false"`
Listings []Listing `gorm:"association_autocreate:false;association_autoupdate:false"`
}
type Rentee struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
UserID uuid.UUID `gorm:"unique;not null;index"`
Verified bool
Bookings []Booking `gorm:"association_autocreate:false;association_autoupdate:false"`
}
then I call this function
func userList(r *queryResolver, id *string) (*gql.Users, error) {
entity := consts.GetTableName(consts.EntityNames.Users)
whereID := "id = ?"
record := &gql.Users{}
dbRecords := []*dbm.User{}
tx := r.ORM.DB.Begin().Preload(consts.EntityNames.UserProfiles)
defer tx.RollbackUnlessCommitted()
if id != nil {
tx = tx.Where(whereID, *id)
}
tx = tx.Find(&dbRecords).Count(&record.Count)
for _, dbRec := range dbRecords {
renter := dbm.Renter{}
tx = tx.Model(&dbRec).Related(&renter)
logger.Infof("%+v", dbRec.Renter)
// rentee := dbm.Rentee{}
// tx.Related(&rentee)
// logger.Info(rentee)
if rec, err := tf.DBUserToGQLUser(dbRec); err != nil {
logger.Errorfn(entity, err)
} else {
record.List = append(record.List, rec)
}
}
return record, tx.Error
}
If I get rid of tx = tx.Model(&dbRec).Related(&renter) the query runs, the profile object loads but my renter and rentee object doesn't have the data from database. And I notice it doesn't run the query SELECT * FROM "renters" WHERE "renters"."deleted_at" IS NULL AND (("user_id" = 'my-user-uuid'))
I also tried to this this:
tx = tx.Preload(consts.EntityNames.Renters).Preload(consts.EntityNames.Rentees).Preload(consts.EntityNames.UserProfiles).Find(&dbRecords).Count(&record.Count)
but get thise error: can't preload field Renters for models.User
I have People and Data , where People has one Data and Data belongs to People
how to make a request body JSON for that Association in go gin? I am using gorm for this case,
the documentation of gorm is not clear for me for this case,
i was supposed like
func CreateTodo(db *gorm.DB) func(c *gin.Context) {
var person Person
var data Data
c.bindJSON(&Person)
c.bindJSON(&Data)
db.create(&Person)
db.create(&Data)
c.JSON(200, gin.H{ result : []interface{person, data})
}
type (
Data struct {
ID uint `gorm:"auto_increment"`
PersonID uint
Person *Person `gorm:"foreignkey:PersonID;association_foreignkey:id"`
Address string
Languange string
}
Person struct {
gorm.Model
Email string `gorm:"type:varchar(100);unique_index;not null"`
Password string `gorm:"type:varchar(100);not null"`
Role string `gorm:"size:30;not null"`
DataID uint
Data *Data `gorm:""foreignkey:DataID;association_foreignkey:id"`
}
)
I am sure it will not make the person_id and data_id for FK
what I ask, how I can make the request body for that Association until those request created with FK itself ? should I create then update again for person_id and data_id after it created ??
Gorm will do almost everything for an association link. It seems that "DataID" in your Person struct is useless. See the code below for an example:
package main
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type (
Data struct {
ID uint `gorm:"auto_increment"`
PersonID uint
Person *Person `gorm:"foreignkey:PersonID;association_foreignkey:id"`
Address string
Languange string
}
Person struct {
gorm.Model
Email string `gorm:"type:varchar(100);unique_index;not null"`
Password string `gorm:"type:varchar(100);not null"`
Role string `gorm:"size:30;not null"`
Data *Data `gorm:""foreignkey:PersonID;association_foreignkey:id"`
}
)
func main() {
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
panic("failed to connect database")
}
db.LogMode(true)
defer db.Close()
// Migrate the schema
db.AutoMigrate(&Person{}, &Data{})
data := &Data{
Address: "Shanghai,China",
Languange: "Chinese",
}
person := &Person{
Email: "zhjw43#163.com",
Data: data,
}
db.Save(person)
db.DropTable("data", "people")
}
I'm trying to write data to PostgreSQL database with relationships using this code.
req := c.Request()
tx := c.Value("tx").(*pop.Connection)
user := &models.User{}
if req.FormValue("user_type") == "0" {
salt, _ := strconv.Atoi(os.Getenv("SALT"))
userType, _ := strconv.Atoi(req.FormValue("user_type"))
user.UserType = int16(userType)
user.Phone, _ = strconv.ParseInt(req.FormValue("phone"), 10, 64)
hash, _ := bcrypt.GenerateFromPassword([]byte(req.FormValue("password")), salt)
user.PassHash = string(hash)
user.Balance = 0
user.Validated = false
user.Person = &models.Person{
Name: req.FormValue("name"),
City: req.FormValue("city"),
}
} else {
}
err := tx.Eager().Create(user)
fmt.Println(errors.WithStack(err))
return c.Render(200, r.JSON(map[string]string{"message": "User successfully created"}))
User model:
type User struct {
ID int64 `db:"id" rw:"w" form:"-"`
UserType int16 `db:"user_type" form:"-"`
Phone int64 `db:"phone"`
PassHash string `db:"pass_hash" form:"-"`
Balance int64 `db:"balance" form:"-"`
Avatar nulls.String `db:"avatar" form:"-"`
Validated bool `db:"validated" form:"-"`
//Company *Company `has_one:"company" fk_id:"user_id"`
Person *Person `has_one:"person" fk_id:"user_id"`
}
Person model:
type Person struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email nulls.String `db:"email"`
City string `db:"city"`
UserId int64 `db:"user_id"`
User *User `belongs_to:"user"`
}
tx.Eager().Create is only creating a row for User model. Row for Person model is not being created. When trying to print, the following error occurs without a stack trace.
could not set '%!s(int64=15)' to '<invalid reflect.Value>'
Any help will be appreciated.
I'm trying to create a basic commenting api in go. I can't seem to figure out how to scan postgresql arrays into an array of structs within a struct. I think I could probably have Thread.Posts type be jsonb but that seems inelegant since I would have to unmarshall it I think.
sql: Scan error on column index 3, name "posts": unsupported Scan,
storing driver.Value type []uint8 into type *[]models.Post
var threadSchema = `
CREATE TABLE IF NOT EXISTS thread (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
profile_id INTEGER REFERENCES profile (id)
)`
var postSchema = `
CREATE TABLE IF NOT EXISTS post (
id SERIAL PRIMARY KEY,
comment TEXT,
profile_id INTEGER REFERENCES profile (id),
thread_id INTEGER REFERENCES thread (id)
)`
type Post struct {
Id int `db:"id" json:"id"`
Comment string `db:"comment" json:"comment" binding:"required" form:"comment"`
ProfileId int `db:"profile_id" json:"profile_id" binding:"required" form:"profile_id"`
ThreadId int `db:"thread_id" json:"thread_id" binding:"required" form:"thread_id"`
}
type Thread struct {
Id int `db:"id" json:"id"`
Name string `db:"name" json:"name" binding:"required" form:"name"`
ProfileId int `db:"profile_id" json:"profile_id" binding:"required" form:"profile_id"`
Posts []Post `db:"posts" json:"posts" form:"posts"`
}
func GetThreads(db *sqlx.DB, c *gin.Context) {
threads := []Thread{}
err := db.Select(&threads, `
SELECT thread.id,thread.name,thread.profile_id,array_agg(post.id) AS posts
FROM thread
INNER JOIN post ON thread.id = post.thread_id
GROUP BY thread.id;
`)
if err != nil {
log.Fatal(err)
}
c.JSON(http.StatusOK, gin.H{"data": threads})
}
You could define your type:
type Posts []Post
// Scan implements the sql.Scanner interface.
func (a *Posts) Scan(src interface{}) error {
// ...
}
// Value implements the driver.Valuer interface.
func (a Posts) Value() (driver.Value, error) {
// ...
}
For more information on the implementation see eg here
First off, you can't do this with sqlx, whether or not you're using Postgres arrays.
Second, your SQL query is simply aggregating Post IDs, not the content of the posts, so there's no way to get the data you want (using Go or otherwise).
So here's what you can do:
Use an anonymous embedded struct, capture all of the Post content in your SQL query, and then merge your duplicated Threads.
type Post struct {
Id int `db:"id" json:"id"`
Comment string `db:"comment" json:"comment" binding:"required" form:"comment"`
ProfileId int `db:"profile_id" json:"profile_id" binding:"required" form:"profile_id"`
ThreadId int `db:"thread_id" json:"thread_id" binding:"required" form:"thread_id"`
}
type ThreadDb struct {
Id int `db:"id" json:"id"`
Name string `db:"name" json:"name" binding:"required" form:"name"`
ProfileId int `db:"profile_id" json:"profile_id" binding:"required" form:"profile_id"`
Post
}
type Thread struct {
Id int `db:"id" json:"id"`
Name string `db:"name" json:"name" binding:"required" form:"name"`
ProfileId int `db:"profile_id" json:"profile_id" binding:"required" form:"profile_id"`
Posts []Post `db:"posts" json:"posts" form:"posts"`
}
func GetThreads(db *sqlx.DB, c *gin.Context) {
threads := []ThreadDb{}
err := db.Select(&threads, `
SELECT thread.id,thread.name,thread.profile_id,post.id,post.comment,post.profile_id,post.thread_id
FROM thread
INNER JOIN post ON thread.id = post.thread_id
GROUP BY post.id;
`)
thread_map := make(map[string]Thread)
for i, thread := range threads {
if _, ok := thread_map[thread.Id]; ok {
thread_map[thread.Id].Posts = append(thread_map[thread.Id].Posts, thread.Post)
} else {
thread_map[thread.Id] = Thread{thread.Id, thread.Name, thread.ProfileId, []Post{thread.Post}}
}
}
var threadSlice []string
for k := range thread_map {
threadSlice = append(threadSlice, k)
}
if err != nil {
log.Fatal(err)
}
c.JSON(http.StatusOK, gin.H{"data": threadSlice})
}
Use GROUP_CONCAT or similar. I wouldn't recommend unless you plan on having a maximum of about 100 posts per thread.