Can't get GORM associations to work as expected - go

My two models are
package models
// Business ...
type Business struct {
ID uint
Name string `gorm:"not null"`
Tables Tables `gorm:"ForeignKey:BusinessID"`
}
// Businesses ...
type Businesses []Business
and
package models
// Table ...
type Table struct {
ID uint
Ref string `gorm:"not null"`
Business Business
BusinessID uint
}
// Tables ...
type Tables []Table
It may be obvious from the code, but the association should be that one 'business' has many 'tables' and a 'table' belong to a 'business'. However, when the database is created there are no foreign keys created (I am using sqlite3) and when I return the business which has been created with
bus := models.Business{
Name: "Test",
Tables: models.Tables{
models.Table{Ref: "A1"},
},
}
db.Create(&bus)
the businesses array is empty, and when the table is returned although the business_id is correct, there is business struct is empty also.

I could not reproduce your problem. I have a working solution here. I suspected that it would not work with the entities in a separate models package, but that worked as well.
package main
import (
"log"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
_ "github.com/mattn/go-sqlite3"
)
type Business struct {
ID uint
Name string `gorm:"not null"`
Tables Tables `gorm:"ForeignKey:BusinessID"`
}
type Table struct {
ID uint
Ref string `gorm:"not null"`
Business Business
BusinessID uint
}
type Tables []Table
type Businesses []Business
func main() {
var err error
var db *gorm.DB
db, err = gorm.Open("sqlite3", "test.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.LogMode(true)
db.AutoMigrate(&Business{})
db.AutoMigrate(&Table{})
bus := Business{
Name: "Test",
Tables: Tables{
Table{Ref: "A1"},
},
}
db.Create(&bus)
var businesses Businesses
db.Preload("Tables").Find(&businesses)
log.Println(businesses)
}

Related

how to handle INSERT on conflict in a has many association in GORM Create, ERROR (SQLSTATE 23505)

I have two models as follows:
type OHLCV struct {
gorm.Model
Interval string `gorm:"uniqueIndex:idx_ohlcv"`
Pair string `gorm:"uniqueIndex:idx_ohlcv"`
OpenTime time.Time `gorm:"uniqueIndex:idx_ohlcv"`
CloseTime time.Time `gorm:"uniqueIndex:idx_ohlcv"`
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Close float64 `json:"close"`
Volume float64 `json:"volume"`
QuoteAssetVolume float64 `json:"quoteAssetVolume"`
NumberOfTrades float64 `json:"numberOfTrades"`
Calculations []Calculation `gorm:"foreignKey:OhlcvRefer"`
}
and
type Calculation struct {
gorm.Model
OhlcvRefer uint `gorm:"uniqueIndex:idx_calculation"`
Key string `gorm:"uniqueIndex:idx_calculation"`
Config string `gorm:"uniqueIndex:idx_calculation"`
Value float64
}
As you see both tables have unique indexes to prevent inserting duplicate data. The first table foreignKey is a part of the second table's unique index. The problem is how can I handle ON CONFLICT DO NOTHING behavior for both tables with a single GORM Create statement?
Before adding the Calculation association I was able handle CONFLICTS with
err = db.Clauses(clause.OnConflict{DoNothing: true,
Columns: []clause.Column{{Name: "interval"}, {Name: "pair"}, {Name: "open_time"}, {Name: "close_time"}},
}).Create(ohlcvs).Error
But now I get the following error:
ERROR: duplicate key value violates unique constraint "idx_calculation" (SQLSTATE 23505)
What I need is to DO NOTHING for the Calculation conflicts as well.
To achieve what you need, it should be enough to use the Unique index constraint on the two structs. Let's see how you can implement it.
package main
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type User struct {
Id int
Name string `gorm:"uniqueIndex:idx_name"`
Posts []Post
}
type Post struct {
Id int
Title string `gorm:"uniqueIndex:idx_title"`
UserId int
}
func main() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Post{})
db.AutoMigrate(&User{})
db.Create(&User{
Name: "john doe",
Posts: []Post{
{Title: "first"},
{Title: "second"}, // to generate an error change to "first"
},
})
}
In this way, if you're entering duplicates value the db itself will block you. This is valid ono either on the users table and on the posts one. IMO, it's a very clean approach and you can be as flexible as you wish.
Let me know if this solves your issue or there is something else!

gorm, foreign keys, and embedded structs

Gorm's half-baked, magic-out-the-box support of foreign keys has been an annoyance for years and I'm finally trying to figure it out once and for all. I'm using Postgres 12, gorm 1.23.3 and go 1.18.
I have a base model similar to gorm.Model but with a little bit extra:
type BaseModel struct {
ID string `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
InstanceVersion int `json:"instanceVersion"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
DeletedAt *time.Time `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
CreatedBy string `json:"createdBy"`
UpdatedBy string `json:"updatedBy"`
DeletedBy string `json:"deletedBy,omitempty"`
MetaData json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`
}
Every model in my DB uses this BaseModel as follows:
type Profile struct {
BaseModel
Name string `json:"name"`
UserID string `json:"userId"`
}
It generates the tables as follows (UML generated by DBeaver and double checked to be true):
I'm trying to add a foreign key to the CreatedBy and UpdatedBy columns such that they must point to an existing Profile. So I add the following field to the BaseModel type:
CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
I expected the foreign key to be created for every model that BaseModel is a part of and point back to the Profiles table. However, it only makes the FK on the Profiles table.
Minimal recreation of the problem:
package main
import (
"encoding/json"
"time"
"github.com/lib/pq"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type BaseModel struct {
ID string `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
InstanceVersion int `json:"instanceVersion"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
DeletedAt *time.Time `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
CreatedBy string `json:"createdBy"`
UpdatedBy string `json:"updatedBy"`
DeletedBy *string `json:"deletedBy,omitempty"`
MetaData json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`
CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
}
type ActivityType string
type Activity struct {
Base BaseModel `gorm:"embedded"`
Type ActivityType `json:"type"`
Message string `json:"message"`
Content string `json:"content"`
ImageUrl string `json:"imageUrl"`
DisplayProfileIds pq.StringArray `json:"displayProfileIds" gorm:"type:uuid[]"`
RecipientProfileIds pq.StringArray `json:"recipientProfileIds" gorm:"-"`
// Preload
ActivityProfiles []*ActivityProfile `json:"activityProfiles"` // has many
}
type ActivityProfile struct {
Base BaseModel `gorm:"embedded"`
ReadAt *time.Time `json:"readAt,omitempty" gorm:"type:timestamp" sql:"index"`
Route string `json:"route"`
ActivityID string `json:"activityId"`
ProfileID string `json:"profileId"`
// Preload
Activity *Activity `json:"activity"` // belongs to
Profile *Profile `json:"profile"` // belongs to
}
type Profile struct {
BaseModel
Name string `json:"name"`
UserID string `json:"userId"`
}
func main() {
db, err := gorm.Open(postgres.Open("host=localhost port=5432 user=corey dbname=corey password= sslmode=disable"))
if err != nil {
panic(err)
}
models := []interface{}{
&Activity{},
&ActivityProfile{},
&Profile{},
}
err = db.AutoMigrate(models...)
if err != nil {
panic(err)
}
}
I've also tried using gorm:"embedded" tags instead of nested structs but it didn't help. Nothing in this question helps: DB.Model(...).AddForeignKey(...) doesn't exist anymore, the db.Migrator().CreateConstraint(...) lines don't work (how does it know what column is the FK and which column it matches to of what other type? Do I have to run both lines? How could this ever possibly work?!?), and I don't want OnUpdate or OnDelete constraints, just foreign keys.
If I put the CreatedByProfile field on Activity, then I get a FK where Profile.CreatedBy is an FK to Activity.ID which is 100% backwards.
I can add this field to my Profile model:
Activities []*Activity `json:"-" gorm:"foreignKey:CreatedBy"`
and it creates the FK like I want, but I don't actually want that field present on the model. Plus, I'd have to add this unnecessary boilerplate for every model in my DB (and _ fields don't end up with the FK being made).
How can I make Gorm do simple things like create foreign keys without decorating my models with unused fields?

One-to-many association

I'm having troubles with one-to-many associations in GORM.
I have those two structures and I'd like to get one patient's full history. Here is my sample code:
type Patient struct {
gorm.Model
Prenom string `json:"prenom" gorm:"column:patient_prenom"`
Nom string `json:"nom" gorm:"column:patient_nom"`
Genre string `json:"genre" gorm:"column:patient_genre"`
Naissance string `json:"naissance" gorm:"column:patient_naissance"`
Historique []Historique `gorm:"ForeignKey:Fk_patient_id"`
}
type Historique struct {
Fk_patient_id string
Date_consultation string
Fk_maladie_id uint
Fk_compte_medecin_id uint
Patient Patient
}
func GetPatientWithDiseases(id uint) (*Patient, error) {
patient := &Patient{}
//The line right there works so i can retrieve without the history
//err := GetDB().Find(patient, id).Error
db := GetDB().Preload("tt_historique").Find(patient)
err := db.Error
if err != nil {
return nil, err
}
return patient, nil
}
Where "Historique" uses the foreign key of the patient (Fk_patient_id), and the Historique []Historique is the list of every Historique that should end up in the Patient struct after the query.
However I get this error can't preload field tt_historique for models.Patient. I've tried multiple syntaxes that I've found on Internet in the gorm specifications in the struct but nothing worked. I've only been developing using GO for 3 days and GORM is my first ORM, so maybe I'm missing something really obvious.
Based on the presumption that tt_historique is your table name, there are a couple of things you need to take care of here.
By convention, go-gorm uses pluralized snake case struct names as database tables when constructing a SQL query. In your case, to preload the Historique []Historique field, it would look for the historiques table.
To override this, you need to implement the Tabler interface:
type Patient struct {
gorm.Model
Prenom string `json:"prenom" gorm:"column:patient_prenom"`
Nom string `json:"nom" gorm:"column:patient_nom"`
Genre string `json:"genre" gorm:"column:patient_genre"`
Naissance string `json:"naissance" gorm:"column:patient_naissance"`
Historique []Historique `gorm:"foreignKey:Fk_patient_id"`
}
type Historique struct {
Fk_patient_id string
Date_consultation string
Fk_maladie_id uint
Fk_compte_medecin_id uint
Patient Patient
}
func (Historique) TableName() string {
return "tt_historique"
}
Then, your query would look like this:
db := GetDB().Preload("Historique").Find(patient)

how query to ManyToMany field in gorm

I'm using gorm to handle database queries and I have 2 models (ManyToMany) :
type Person struct {
ID uint `json:"id" gorm:"primary_key;unique;autoIncrement"`
Name string `json:"name" binding:"required"`
Family string `json:"family" binding:"required"`
Companies []Company `json:"companies" gorm:"many2many:person_companies;"`
}
type Company struct {
ID uint `json:"id" gorm:"primary_key;unique;autoIncrement"`
Name string `json:"name"`
cars []Car
}
i use this query for receive list of my users:
func GetAllPeople() *[]domain.Person {
var people []domain.Person
db.Find(&people)
return &people
}
this works but shows me Null for companies
{
"id": 0,
"name": "erfan",
"family": "",
"companies": null
}
what should I use in query to show users companies (id) in a list?
You will have to use the Preload method with custom loading to just load the company ID's into the Companies field.
func GetAllPeople() *[]domain.Person {
var people []domain.Person
tx := db.Preload("Companies", func(db *gorm.DB) *gorm.DB {
return db.Select("ID")
}).Find(&people)
if tx.Error != nil {
// handle error
}
return &people
}
You can find more details on this link or this question.

Golang Gorm reverse hasMany relation

We can Easy to get the child of hasMany relation in Golang Gorm with Preload.
But how to get reverse of relation.
type Owner struct {
ID int `gorm:"column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
Projects []Project `gorm:"foreignkey:OwnerID" json:"projects"`
}
type Project struct {
ID int `gorm:"column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
OwnerID int `gorm:"column:owner_id" json:"owner_id"`
Gallery []Gallery `gorm:"foreignkey:ProjectID" json:"gallery"`
}
type Gallery struct {
ID int `gorm:"column:id" json:"id"`
ProjectID int `gorm:"column:project_id" json:"project_id"`
Url string `gorm:"column:url" json:"url"`
Title string `gorm:"column:title" json:"title"`
Description string `gorm:"column:description" json:"description"`
}
we can populate Gallery inside Project with Preload, like this:
db.Preload("Gallery").Find(&project)
How to get reverse, we want load project from gallery or owner from project?
the result I want some thing like this, when get project in form of json:
{
"id": 1,
"name": "Name Of Project",
"owner": {
"id":1,
"name": "Owner 1"
},
"gallery":[]
}
I am a bit late to the game with this answer, but you can define inverse relationships (belongs to) on the child models by doing this:
type ParentModel struct {
Children []Child // This is a 'has many'
}
type Child struct {
ParentModelID int // These behave as a 'belongs to'
ParentModel ParentModel // You need both the model and the modelID
}
Full working example
This uses the models from your question. A note on performance, the preloading in gorm isn't done via joins. It's done via additional SQL queries, the more nesting the more queries. If you have commonly executed preloading, it could be worth writing a raw query and a customer scanner to use an optimised query.
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Owner struct {
gorm.Model
Projects []Project
}
type Project struct {
gorm.Model
OwnerID int
Owner Owner
Gallery []Gallery
}
type Gallery struct {
gorm.Model
ProjectID int
Project Project
}
func main() {
db, err := gorm.Open(sqlite.Open("many2many.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
err = db.AutoMigrate(&Owner{}, &Project{}, &Gallery{})
if err != nil {
return
}
ownerOne := Owner{}
db.Create(&ownerOne)
projectOne := Project{Owner: ownerOne}
projectTwo := Project{Owner: ownerOne}
db.Create(&projectOne)
db.Create(&projectTwo)
galleryOne := Gallery{Project: projectOne}
galleryTwo := Gallery{Project: projectOne}
galleryThree := Gallery{Project: projectTwo}
galleryFour := Gallery{Project: projectTwo}
db.Create(&galleryOne)
db.Create(&galleryTwo)
db.Create(&galleryThree)
db.Create(&galleryFour)
// Find by project and preload owners
fetchedProject := Project{}
db.Preload("Owner").Find(&fetchedProject, projectOne.ID)
fmt.Println(fetchedProject.Owner)
// Find by a gallery and preload project and owner
fetchedGallery := Gallery{}
db.Preload("Project.Owner").Find(&fetchedGallery, galleryOne.ID)
fmt.Printf("Gallery.id = %d --> Project.id = %d --> Owner.id = %d\n", fetchedGallery.ID,
fetchedGallery.Project.ID, fetchedGallery.Project.Owner.ID)
}

Resources