Update HasMany Association failing Gorm - postgresql

When I try to update the Shoppinglist struct with the data I get an "there is no unique or exclusion constraint matching the ON CONFLICT specification (SQLSTATE 42P10)" Error
These are my Structs
type Shoppinglist struct {
Model
ID int `gorm:"primaryKey" json:"id"`
Title string `json:"title"`
Items []Item `json:"items" gorm:"foreignKey:ParentListID;references:ID;"`
Owner string `json:"owner"`
Participants pq.StringArray `gorm:"type:text[]" json:"participants"`
}
type Item struct {
Model
ParentListID int `gorm:"primaryKey" json:"parentListId"`
Title string `json:"title"`
Position int `json:"position"`
Bought bool `json:"bought"`
}
And this is the Code I execute when trying to edit a list
func EditList(id int, data map[string]interface{}) error {
//https://github.com/go-gorm/gorm/issues/3487
shoppinglist := Shoppinglist{
ID: data["id"].(int),
Title: data["title"].(string),
Items: data["items"].([]Item),
Owner: data["owner"].(string),
Participants: data["participants"].([]string),
}
if err := db.Session(&gorm.Session{FullSaveAssociations: true}).Where("id = ?", id).Updates(&shoppinglist).Error; err != nil {
return err
}
return nil
}
This is where I execute the EditList and where I set all the values to pass nito the map:
type Shoppinglist struct {
ID int
Title string
Items []models.Item
Owner string
Participants []string
PageNum int
PageSize int
}
func (s *Shoppinglist) Edit() error {
shoppinglist := map[string]interface{}{
"id": s.ID,
"title": s.Title,
"items": s.Items,
"owner": s.Owner,
"participants": s.Participants,
}
return models.EditList(s.ID, shoppinglist)
}
Before I was just using a []string instead of []Item and that was working perfectly. Now everything updates except for the []Item
These are the SQL Queries executed:
UPDATE "shoppinglists" SET "modified_on"=1628251977096,"title"='kjhdsfgnb',"owner"='janburzinski1#gmail.com',"participants"='{}' WHERE id = 517687 AND "id" = 517687
INSERT INTO "items" ("created_on","modified_on","deleted_at","title","position","bought","parent_list_id") VALUES (1628251977,1628251977116,NULL,'dfkjhgndfjkg',1,false,517687),(1628251977,1628251977116,NULL,'dfgh123',2,true,517687) ON CONFLICT ("parent_list_id") DO UPDATE SET "created_on"="excluded"."created_on","modified_on"="excluded"."modified_on","deleted_at"="excluded"."deleted_at","title"="excluded"."title","position"="excluded"."position","bought"="excluded"."bought" RETURNING "parent_list_id"
I would really like to know how to Update a Relation in Gorm or why this isn't working because I've been looking through all the Association Issues on Github and Stackoverflow and didn't find a answer that worked for me.

The first problem I see here is that your Item has no ID but uses the ParentListID as primary key. That means you can only have one Item for each parent which defeats the purpose of having an array.
Create an ID field (used as primary key) for items and if there's still issues with your approach, please update the question.
PS: would have left this in a comment, but can't.

I just needed to add the * to the []Item and fix the problem with the primarykey and remove the reference.
type Shoppinglist struct {
Model
ID int `gorm:"primaryKey" json:"id"`
Title string `json:"title"`
Items []*Item `json:"items" gorm:"foreignKey:ParentListID;"`
Owner string `json:"owner"`
Participants pq.StringArray `gorm:"type:text[]" json:"participants"`
}
type Item struct {
Model
ID int `gorm:"primaryKey" json:"id"`
ParentListID int `json:"parentListId"`
ItemID int `json:"itemId"`
Title string `json:"title"`
Position int `json:"position"`
Bought bool `json:"bought" gorm:"default:false"`
}

Related

How to save array of structs

I am trying to save an Array of Structs.
I tried:
type ShiftValue struct {
Hour uint8 `json:"hour"`
Minute uint8 `json:"minute"`
}
type Shift struct {
Start ShiftValue `json:"start"`
End ShiftValue `json:"end"`
}
type Config struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;index;" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
Shifts []Shift `gorm:"type:varchar(100)[];" json:"shifts,"`
}
But is not working. I also tried to save Shifts as pq.StringArray:
type Config struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;index;" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
Shifts pq.StringArray `gorm:"type:varchar(100)[];" json:"shifts,"`
}
Which is kind of working, but I don't know how can I convert a slice of Shift to StringArray.
Should I use GenericArrray?
How can I make the conversion from Slice to GenericArray or StringArray?
When I Unmarshall the data, I do it in the following struct, I validate the data, and after that I want to save it to Database:
type ConfigUpdate struct {
Shifts []Shift `json:"shifts,"`
}
The nearest scenario that can be seen in gorm embedded struct test is
package gorm_test
import "testing"
type BasePost struct {
Id int64
Title string
URL string
}
type Author struct {
ID string
Name string
Email string
}
type HNPost struct {
BasePost
Author `gorm:"embedded_prefix:user_"` // Embedded struct
Upvotes int32
}
type EngadgetPost struct {
BasePost BasePost `gorm:"embedded"`
Author Author `gorm:"embedded;embedded_prefix:author_"` // Embedded struct
ImageUrl string
}
As can be seen, all of these Base Struct have Id to be referred to as foreign key in parent struct.
One more scenario that can be found handled in one of StackOverflow another answer.
type Children struct {
Lat float64
Lng float64
}
type ChildArray []Children
func (sla *ChildArray) Scan(src interface{}) error {
return json.Unmarshal(src.([]byte), &sla)
}
func (sla ChildArray) Value() (driver.Value, error) {
val, err := json.Marshal(sla)
return string(val), err
}
type Parent struct {
*gorm.Model
Childrens ChildArray `gorm:"column:childrens;type:longtext"`
}
Please verify it on your own as I don't have a gorm set up. I just did the R & D part. I hope, it will help many of us. thanks

How to load structs with multiple nested association

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

how to make request body in gorm for Association belongs to and has one

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")
}

sqlx scan postgres array into struct

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.

How to modify fields of a Golang struct to another type before rendering to jSON?

We are adding an include parameter in our API w/c API clients can use to include relationships
// request
GET /api/events?include=team1
[{
"id": <event_id>,
"name": <event_name>,
"team1": {
"id": <team_id>,
"name": <team_name>
}
}]
// code
type Event struct {
ID int64 `gorm:"primary_key" json:"id"`
Team1ID int64 `json:"-"`
Team1 Team `json:"team1"`
}
var event Event
Db.Preload("Team1").Find(&event, 1)
c.JSON(http.StatusOK, event)
But we also want to be able to do this:
// request
GET /api/events
[{
"id": <event_id>,
"name": <event_name>,
"team1": <team1_id>
}]
The team1 field is now just an id.
Is there an easy way to do this in Go?
I think I can do this by using a map[string]interface{}, like after fetching the events in the db, convert the event structs to a map[string]interface{} and do the modifications. But I'm wondering if theres an easier solution.
Here's my attempt in using map[string]interface{} - https://play.golang.org/p/-19MWtqhE3 . The code is very verbose. The idea is to use map[string]interface{} for every struct then compose the top level resource that includes the related resources.
What's a better way to do this?
Actually you could just set the team1 to be an interface and then cast it's value, obviously making the proper validations.
type Event struct {
ID int64 `gorm:"primary_key" json:"id"`
Team1ID int64 `json:"-"`
Team1 interface{} `json:"team1"`
Team1Struct Team `json:"-"`
}
And then evaluate:
if value, ok := evt.Team1.(Team); ok {
// The passed value is a Team struct
event.Team1Struct = value
} else {
// The passed value is a string (or something else...)
}
I think the most cleaner way is to use two different functions.
type Event struct {
ID int64 `gorm:"primary_key" json:"id"`
Team1ID int64 `json:"-"`
Team1 Team `json:"team1"`
}
type Team struct {
ID int64 `gorm:"primary_key" json:"id"`
Name string `json:"name"`
}
type EventInformations struct {
ID int64 `json:"id"`
Name string `json:"name"`
Team1 `json"team1"`
}
type EventInformationsTeamIncluded struct {
ID int64 `json:"id"`
Name string `json:"name"`
Team1 Team `json:"team1"`
}
func (e *Event) Informations() *EventInformations {
return &EventInformations{
ID: e.ID,
Name: e.Name,
Team1: e.Team1,
},
}
func (e *Event) InformationsTeamIncluded() *EventInformationsTeamIncluded {
return &EventInformations{
ID: e.ID,
Name: e.Name,
Team1: &Team{
...
},
}
}
So later just call
event.Informations();
Or
event.InformationsTeamIncluded();
There is something not very logical in your code/examples:
The var Team1 is a little bit weird cause there is only one value in the struct so if you don't have plan to add some Team in struct I suggest to replace it by Team if you plan to have any Team replace it by Teams []*Team
The second thing is in your example your return two differents values to a same name, it depends of the context I suggest in one case to return team1_id instead of team1
EDIT : without struct
func (e *Event) Informations() *map[string]interface{} {
return &map[string]interface{}{
"ID": e.ID,
"Name": e.Name,
"Team1": e.Team1,
},
}
func (e *Event) InformationsTeamIncluded() *map[string]interface{} {
// Reuse the previous function
data := e.Informations()
// Add the team1 informations
data["team1"] = map[string]interface{}{
ID: e.Team1.ID,
Name: e.Team1.Name,
}
return data
}