There is a way to get all structs in package (entity in this case) to generate an automatic migrations list?
I split entities and migration package and now we have a package dedicated to all entities used by gorm and this is how I manage migration currently, completely manual to each new entity added we have to modify the migration main code adding the new entity to the migrationsList
package entity
type ClockOffset struct {
Model
ID string `json:"id" gorm:"primarykey"`
LastSyncClock uint32 `json:"last_sync_clock" gorm:"not null"`
LastSyncTimestamp uint32 `json:"last_sync_timestamp" gorm:"not null"`
}
type InflightMessage struct {
Model
ID string `json:"id" gorm:"primarykey"`
Token uint8 `gorm:"not null;uniqueIndex:idx_gateway_id_token"`
Tag string `gorm:"not null"`
}
// ... other entities ...
package main
func main() {
var migrationsList []*gormigrate.Migration
migrationsList = append(migrationsList, &gormigrate.Migration{
ID: "001-ClockOffset-CreateTable",
Migrate: func(tx repo.Database) error {
return tx.Migrator().CreateTable(&entity.ClockOffset{})
},
Rollback: func(tx repo.Database) error {
return tx.Migrator().DropTable(&entity.ClockOffset{})
},
})
migrationsList = append(migrationsList, &gormigrate.Migration{
ID: "002-InflightMessage-CreateTable",
Migrate: func(tx repo.Database) error {
return tx.Migrator().CreateTable(&entity.InflightMessage{})
},
Rollback: func(tx repo.Database) error {
return tx.Migrator().DropTable(&entity.InflightMessage{})
},
})
m := gormigrate.New(repo.Db(), gormigrate.DefaultOptions, migrationsList)
if err := m.Migrate(); err != nil {
panic(err)
}
}
I'd like to find an automatic way to read all the structures present on the package entity loop and append them to migrationsList
Related
I am trying to save an hederea contract ID of type *hedera.ContractID into a Gorm field but i get the error "invalid field found for struct github.com/hashgraph/hedera-sdk-go/v2.AccountID's field AliasKey: define a valid foreign key for relations or implement the Valuer interface"
package contract
import (
"fmt"
"github.com/.../scanner/controllers/blockchain"
database "github.com/.../scanner/db"
model "github.com/.../scanner/models"
"github.com/rs/xid"
"gorm.io/gorm"
)
func DeployContract() *gorm.DB {
//connect to database
db, err := database.ConnectToDB()
//if db connection fails
if err != nil {
panic(err)
}
//init model
var modelContract model.Contract
//check if a contract has been deployed
if err := db.First(&modelContract); err.Error != nil {
//no deployment found
//Migrate the schema
db.AutoMigrate(&model.Contract{})
//deploy contract
contract, _ := blockchain.DeployContract()
//create record
// generate random id
id := xid.New()
// Create
db.Create(&model.Contract{
Id: id.String(),
ContractId: contract.Receipt.ContractID,
GasUsed: contract.CallResult.GasUsed,
TransactionId: fmt.Sprint(contract.TransactionID),
Timestamp: contract.ConsensusTimestamp,
ChargeFee: fmt.Sprint(contract.TransactionFee),
PayerAccount: fmt.Sprint(contract.TransactionID.AccountID),
Status: fmt.Sprint(contract.Receipt.Status),
})
}
return db
}
Gorm Model
package models
import (
"time"
"github.com/hashgraph/hedera-sdk-go/v2"
"gorm.io/gorm"
)
type Contract struct {
gorm.Model
Id string
ContractId *hedera.ContractID
GasUsed uint64
TransactionId string
Timestamp time.Time
ChargeFee string
PayerAccount string
Status string
}
For custom data types, you need to specify how the value will be stored and retrieved from your database. This is done by implementing the Scanner and Valuer interfaces.
However, since hedera.ContractID is defined in another package, you will need to create your own ContractID and implement these interfaces. Something like this:
type ContractID hedera.ContractID
type Contract struct {
gorm.Model
Id string
ContractId *ContractID
GasUsed uint64
TransactionId string
Timestamp time.Time
ChargeFee string
PayerAccount string
Status string
}
func (c *ContractID) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to unmarshal ContractID value:", value))
}
return json.Unmarshal(bytes, c)
}
func (c ContractID) Value() (driver.Value, error) {
return json.Marshal(c)
}
Additionally, cast hedera.ContractID into model.ContractID wherever it is used. For example:
cID := model.ContractID(*contract.Receipt.ContractID)
// Create
db.Create(&model.Contract{
Id: id.String(),
ContractId: &cID,
GasUsed: contract.CallResult.GasUsed,
TransactionId: fmt.Sprint(contract.TransactionID),
Timestamp: contract.ConsensusTimestamp,
ChargeFee: fmt.Sprint(contract.TransactionFee),
PayerAccount: fmt.Sprint(contract.TransactionID.AccountID),
Status: fmt.Sprint(contract.Receipt.Status),
})
I have 2 models with Gorm like below:
type Post struct {
*gorm.Model
ID uint32 `gorm: "primarykey"`
PostTitle string `gorm:"posttitle;not null"`
PostSlug string `gorm:"postslug;unique;not null"`
PostCategoryID uint32 `gorm:"postcategoryid"`
PostCollections []PostCollection `gorm:"many2many:post_collection;"`
PostTags []PostTag `gorm:"many2many:post_tag; constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type PostCategory struct {
ID uint32 `gorm: "primarykey"`
PostCatName string `gorm:"postcatname;not null"`
PostCatSlug string `gorm:"postcatslug;unique;not null"`
PostCatParent uint32 `gorm: "postcatparent"`
Posts []Post `gorm:"foreignKey:PostCategoryID"`
}
I want get all posts of a parent category (which has many child categories) with function like below:
func GetPostByCategory(c *fiber.Ctx) error {
db := database.DB //connect database
var post []models.Post
var categories []models.PostCategory
cat := c.Params("catslug") //get slug value of parent category from route
cate := GetCategoryChild(cat) //get array of child category
db.Model(&categories).Where("id IN (?)", cate).Association("Posts").Find(&post)
return c.JSON(post)
}
It return nil array . Please help me to fix this
Problem
It is in your db.Model(&categories), you use an empty array of PostCategory. This will result in this query:
Code:
DB.Model(&categories).Where("id IN (?)", []int{1, 2, 3}).Association("Posts").Find(&post)
Output:
SELECT * FROM `posts` WHERE id IN (1,2,3) AND `posts`.`post_category_id` IN (NULL) AND `posts`.`deleted_at` IS NULL
The "id IN (?)", cate id is not the id of category, but the id of the post instead, because we are using Post type inside the .Find(&post)
You can try to apply logging into Gorm by using this config
databaseConfig := fmt.Sprintf("%s:%s#tcp(%s:%s)/%s?multiStatements=true&parseTime=true", "root", "", "127.0.0.1", "3306", "tester")
DB, _ = gorm.Open(mysql.Open(databaseConfig), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
Solution
Try to insert some data inside your categories
This is My Full Code, I comment several line of your code because no full code was provided.
// https://stackoverflow.com/questions/69977516/unsupported-relations-for-schema-with-has-many-relation-in-gorm
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Post struct {
*gorm.Model
ID uint32 `gorm:"primarykey"`
PostTitle string `gorm:"posttitle;not null"`
PostSlug string `gorm:"postslug;unique;not null"`
PostCategoryID uint32 `gorm:"postcategoryid"`
// PostCollections []PostCollection `gorm:"many2many:post_collection;"`
// PostTags []PostTag `gorm:"many2many:post_tag; constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type PostCategory struct {
ID uint32 `gorm:"primarykey"`
PostCatName string `gorm:"postcatname;not null"`
PostCatSlug string `gorm:"postcatslug;unique;not null"`
PostCatParent uint32 `gorm:"postcatparent"`
Posts []Post `gorm:"foreignKey:PostCategoryID"`
}
var DB *gorm.DB
func main() {
databaseConfig := fmt.Sprintf("%s:%s#tcp(%s:%s)/%s?multiStatements=true&parseTime=true", "root", "", "127.0.0.1", "3306", "tester")
DB, _ = gorm.Open(mysql.Open(databaseConfig), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
sqlDB, _ := DB.DB()
defer sqlDB.Close()
DB.AutoMigrate(&PostCategory{}, &Post{})
// ============ Function Get ================
var categories []PostCategory = []PostCategory{
{
ID: 1,
},
{
ID: 2,
},
}
var post []Post
DB.Model(&categories).Association("Posts").Find(&post)
fmt.Printf("%+v\n", post)
}
Output:
2021/11/23 20:28:41 D:/go/src/udemy-solving/00002/main.go:54
[1.054ms] [rows:3] SELECT * FROM `posts` WHERE id IN (1,2,3) AND `posts`.`post_category_id` IN (1,2) AND `posts`.`deleted_at` IS NULL
[{Model:0xc0001b5380 ID:1 PostTitle:example title 1 PostSlug:ex-1 PostCategoryID:1} {Model:0xc0001b53e0 ID:2 PostTitle:example title 2 PostSlug:ex-2 PostCategoryID:2} {Model:0xc0001b5440 ID:3 PostTitle:example title 3 PostSlug:ex-3 PostCategoryID:1}]
My MySQL Example Data:
post_categories
posts
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.
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)
}
I have struct called User:
type User struct {
Email string
Name string
}
and struct called UserDALModel:
type UserDALModel struct {
gorm.Model
models.User
}
gorm Model looks like this:
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
this is possible to make UserDALModel nested with gorm model and user model so the output will be:
{
ID
CreatedAt
UpdatedAt
DeletedAt
Email
Name
}
now the output is:
{
Model: {
ID
CreatedAt
UpdatedAt
DeletedAt
}
User: {
Name
Email
}
}
According to this test in gorm, I think you need to add an embedded tag to the struct.
type UserDALModel struct {
gorm.Model `gorm:"embedded"`
models.User `gorm:"embedded"`
}
You can also specify a prefix if you want with embedded_prefix.
I found the answer:
type UserModel struct {
Email string
Name string
}
type UserDALModel struct {
gorm.Model
*UserModal
}
------------------------------
user := UserModel{"name", "email#email.com"}
userDALModel := UserDALModel{}
userDal.UserModal = &user
be careful embedding two structs with the same column:
package tests
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"testing"
)
type A struct {
X string
Y string
}
type B struct {
X string
Y string
}
type AB struct {
B B `gorm:"embedded"` // Embedded struct B before struct A
A A `gorm:"embedded"`
}
var DB *gorm.DB
func connectDB() error {
var err error
spec := "slumberuser:password#tcp(localhost:3306)/slumber"
DB, err = gorm.Open("mysql", spec+"?parseTime=true&loc=UTC&charset=utf8")
DB.LogMode(true) // Print SQL statements
//defer DB.Close()
if err != nil {
return err
}
return nil
}
// cd tests
// go test -run TestGormEmbed
func TestGormEmbed(t *testing.T) {
if err := connectDB(); err != nil {
t.Errorf("error connecting to db %v", err)
}
values := []interface{}{&A{}, &B{}}
for _, value := range values {
DB.DropTable(value)
}
if err := DB.AutoMigrate(values...).Error; err != nil {
panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
}
DB.Save(&A{X: "AX1", Y: "AY1"})
DB.Save(&A{X: "AX2", Y: "AY2"})
DB.Save(&B{X: "BX1", Y: "BY1"})
DB.Save(&B{X: "BX2", Y: "BY2"})
//select * from `as` join `bs`;
// # x,y,x,y
// # AX1,AY1,BX1,BY1
// # AX2,AY2,BX1,BY1
// # AX1,AY1,BX2,BY2
// # AX2,AY2,BX2,BY2
var abs []AB
DB.Select("*").
Table("as").
Joins("join bs").
Find(&abs)
for _, ab := range abs {
fmt.Println(ab.A, ab.B)
}
// if it worked it should print
//{AX1 AY1} {BX1 BY1}
//{AX2 AY2} {BX1 BY1}
//{AX1 AY1} {BX2 BY2}
//{AX2 AY2} {BX2 BY2}
// but actually prints
//{BX1 BY1} {AX1 AY1}
//{BX1 BY1} {AX2 AY2}
//{BX2 BY2} {AX1 AY1}
//{BX2 BY2} {AX2 AY2}
}