Unsupported relations for schema with has many relation in GORM - go

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

Related

Gorm Query Customized Join Extra Column

I am trying to get extra columns from a many2many relationships on Gorm. Example
Part
type Part struct {
Id unit
Name string
}
Diagram
type Diagram struct {
Id unit
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
}
DiagramPart
type DiagramPart struct{
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
This is what I have done trying to retrieve PartNumber and PartDescription in Parts.
diagram := &Diagram{}
db := s.db.Where("id = ?", 1).
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
First(diagram)
Unfortunately, I am not able to retrieve part_number, part_description. How should I go about it?
You can add field PartNumber and PartDescription on struct Part OR Diagram, then add tag gorm:"-:migration;->" on than fields to ignore migration and to readonly mode. But on your situation, you can add it in struct Part because you already preload it.
source: https://gorm.io/docs/models.html#Field-Level-Permission
here's the example:
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Part struct {
Id uint `gorm:"primaryKey"`
Name string
PartNumber string `gorm:"-:migration;->"`
PartDescription string `gorm:"-:migration;->"`
}
type Diagram struct {
Id uint `gorm:"primaryKey"`
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
// PartNumber string `gorm:"-:migration;->"`
// PartDescription string `gorm:"-:migration;->"`
}
type DiagramPart struct {
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Diagram{}, &Part{}, &DiagramPart{})
diagram := &Diagram{}
err = db.Debug().Where("id = ?", 1).
// Select("diagrams.*, diagram_parts.part_number, diagram_parts.part_description").
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
// Joins("left join diagram_parts on diagram_parts.diagram_id = diagrams.id").
First(diagram).Error
if err != nil {
panic(err)
}
fmt.Printf("diagram: %v\n", diagram)
fmt.Println("part number:", diagram.Parts[0].PartNumber)
}

How can I query and return only id array by GORM?

I'm now having a problem with getting an id of array feeds from database (Postgres) with Gorm.
How can I query and return id array feeds? I don't know how to get only id from struct without loop
feeds := []models.Feed{}
feedID := []string{}
db.Select("id").Where("user_id = ?", "admin1").Find(&feeds)
for _, feed := range feeds {
feedID = append(feedID, feed.ID)
}
utils.PrintStruct(feeds)
This is feed model file:
type Feed struct {
Model
Status string `json:"status"`
PublishAt *time.Time `json:"publishAt"`
UserID string `json:"userID,omitempty"`
}
This is model base data model using for data entity:
type Model struct {
ID string `json:"id" gorm:"primary_key"`
}
Result:
[
{
"id": "d95d4be5-b53c-4c70-aa09",
"status": "",
"publishAt": null,
"userID":""
},
{
"id": "84b2d46f-a24d-4854-b44d",
"status": "",
"publishAt": null,
"userID":""
}
]
But I want like this:
["d95d4be5-b53c-4c70-aa09","84b2d46f-a24d-4854-b44d"]
You can use pluck
var ids []string
db.Model(&Feed{}).Where("user_id = ?", "admin1").Pluck("id", &ids)

Golang - GORM: sql: Scan error on column index 0, name "row": unsupported Scan, storing driver.Value type string into type *models.User;

Background
The requirement is to use GORM for inserting rows into PostgreSQL but only using raw query.
My codes are as follow:
const userModelFieldsCreateReturn := "id, uuid, slug, username"
// User Model
func (repo *Repository) CreateUsers(ctx *context.Context, users []*models.User) ([]*models.User, error) {
var returnedUsers []*models.User
inserts := []string{}
for _, user := range users {
// Optional Fields
tierIDString := "NULL" // Default
if user.TierID.Valid {
tierIDString = "'" + strconv.FormatInt(user.TierID.Int64, 10) + "'"
}
statusString := "inactive" // Default
if user.Status != nil {
statusString = string(*user.Status)
}
inserts = append(inserts, "('"+user.Slug+"', "+"'"+user.Username+"', "+"'"+user.Email+"', "+"'"+user.Password+"', "+"'"+user.Key+"', "+tierIDString+", "+"'"+statusString+"')")
}
query := fmt.Sprintf(
constants.CreateQuery,
constants.UsersModel,
userModelFieldsCreate,
strings.TrimSuffix(strings.Join(inserts, ", "), ", "),
userModelFieldsCreateReturn,
)
// Debug
fmt.Println("==========")
fmt.Println(query)
fmt.Println("==========")
// Debug
var scannedUsers []interface{}
// results := repo.DBPostgres.Raw(query).Scan(&scannedUsers)
results := repo.DBPostgres.Raw(query).Scan(&returnedUsers)
if results.Error != nil {
results.Assign()
return nil, results.Error
}
// Debug
// for _, scannedUser := range scannedUsers {
// // var user *models.User
// user := &models.User{}
// byte, _ := json.Marshal(scannedUser)
// _ = json.Unmarshal(byte, user)
// }
// Debug
fmt.Println("=====")
fmt.Println(scannedUsers)
fmt.Println(returnedUsers)
fmt.Println("=====")
// Debug
// return users, nil
return returnedUsers, nil
}
Here is the model:
package models
import (
coreConstants "core/constants"
"database/sql"
"database/sql/driver"
"time"
"gorm.io/gorm"
)
type UserStatus string
const (
Active UserStatus = coreConstants.Active
Inactive UserStatus = coreConstants.Inactive
)
func (us *UserStatus) Scan(value string) error {
*us = UserStatus([]byte(value))
return nil
}
func (us UserStatus) Value() (driver.Value, error) {
return string(us), nil
}
type User struct {
ID uint `gorm:"primarykey"`
UUID string `gorm:"not null;default:md5(random()::text || clock_timestamp()::text)::uuid"`
Slug string `gorm:"not null"`
Username string `gorm:"not null"`
Email string `gorm:"not null"`
Password string `gorm:"not null"`
Key string
TierID sql.NullInt64 // Optional
Tier *Tier
Status *UserStatus `gorm:"not null;default:'inactive'" sql:"type:user_status"` // Have Default
CreatedAt time.Time `gorm:"not null;default:current_timestamp"`
UpdatedAt time.Time `gorm:"not null;default:current_timestamp"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
The query is as follows (I have tried to execute this query into the database and it ran as expected):
INSERT INTO users (
slug, username, email, password, key, tier_id, status
)
VALUES
('notalentgeek', 'notalentgeek', 'notalentgeek#gmail.com', '$2a$08$B/.rN7cCv4Ii7VHjl1ZppObUfZeCfae2vPvi4PH1siIp4/JOWNo/u', 'b7a1bd7ca3eb87b80685e5957d676609', NULL, 'inactive'), ('notalentgeek', 'notalentgeek', 'mikael.pratama#yahoo.com', '$2a$08$vrK1Dh9RgqJusoyLPxDLuez8/RZEjZNvyBgImw34QXu3VaN6Ht36m', '7802676f557266524253d04e17ef36aa', NULL, 'inactive')
RETURNING (id, uuid, slug, username);
Error
Error happened at line results := repo.DBPostgres.Raw(query).Scan(&returnedUsers) :
sql: Scan error on column index 0, name "row": unsupported Scan, storing driver.Value type string into type *models.User;
It seems that the RETURNING values from the query can't be scanned into []*models.User .
I can use []interface{} :
// Debug
var scannedUsers []interface{}
results := repo.DBPostgres.Raw(query).Scan(&scannedUsers)
Instead of using []*models.User :
var returnedUsers []*models.User
results := repo.DBPostgres.Raw(query).Scan(&returnedUsers)
And:
fmt.Println(scannedUsers)
Will return:
[(3,2320a7e6-d675-bb19-bd8c-84af1c381b39,notalentgeek,notalentgeek) (4,6b69d37c-e17c-1ce1-d47f-ffe3a3e09a8f,notalentgeek,notalentgeek)]
No error, but not a type I want.
Edits
In the query I tried to create multiple Users at once to the database.
If I change RETURNING (id, uuid, slug, username) into RETURNING (id) (just RETURNING a value) the error doesn't happen and the request succeed.

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.

Access joined fields in query result

I have a query with 2 Joins to other tables, the query executes and returns all the fields from the primary table but I am not sure how I can get access to the joined fields.
in func LastTest() I am setting the var result to the TestResultDetail table, I would also like access to TestResult and SeqTestStage values in my result which are joined in the query.
Post query I can access all of the result fields in the TestResult table but none of the tables I joined on.
TestResultDetail has a FK relation to TestResult.
There are many TestResultDetail records for a single TestResult record.
For example seq_test_stage.description or any field in seq_test_stage comes over as empty.
main.go
package main
import (
"./models"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
)
func main() {
router := gin.Default()
opt := router.Group("opt/v2")
{
opt.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
opt.GET("/last-completed-test/:serial", LastTest)
}
// Add API handlers here
router.Run(":3000")
}
func LastTest(c *gin.Context) {
// Connection to the database
db := models.InitDB()
defer db.Close()
var result models.TestResultDetail
type Response struct {
Status string
Id int64
Serial string
Product string
Stage string
}
// get param and query
serial := c.Params.ByName("serial")
db.Model(&models.TestResultDetail{}).
Select("test_result.id, test_result.serial, test_result.product, test_result_detail.stage_id, seq_test_stage.description").
Joins("join test_result on test_result.ID = test_result_detail.result_id").
Joins("join seq_test_stage on seq_test_stage.ID = test_result_detail.stage_id").
Where("test_result.serial = ?", serial).
Order("test_result_detail.id desc").
Limit(1).
Scan(&result)
if result.ID != 0 {
res1 := &Response{
Status: "OK",
Id: result.ResultId.ID,
Serial: result.ResultId.Serial,
Product: result.ResultId.Product,
Stage: result.StageId.Description,
}
c.JSON(200, res1)
} else {
// Display JSON error
c.JSON(404, gin.H{"error": "No Records", "code": 404})
}
}
test_result_detail.go
package models
import (
"time"
)
type TestResultDetail struct {
ID int64 `gorm:"primary_key" db:"id" json:"id"`
ResultId TestResult
StatusId ResultStatus
StationId Station
StageId SeqTestStage
OperatorId AuthUser
Failstep string `db:"fail_step" json:"fail_step"`
Shift string `db:"shift" json:"shift"`
SequenceRev int `db:"sequence_rev" json:"sequence_rev"`
DateAdded time.Time `db:"date_added" json:"date_added"`
DateTimestamp time.Time `db:"date_timestamp" json:"date_timestamp"`
DateTime time.Time `db:"date_time" json:"date_time"`
StageOrder int `db:"stage_order" json:"stage_order"`
SerialNumber string `db:"serial_number" json:"serial_number"`
IsRetest int `db:"is_retest" json:"is_retest"`
RetestReason string `db:"retest_reason" json:"retest_reason"`
}
test_result.go
package models
import (
"time"
)
type TestResult struct {
ID int64 `gorm:"primary_key" db:"id" json:"id"`
DateAdded time.Time `db:"date_added" json:"date_added"`
Serial string `db:"serial" json:"serial"`
SequenceId int `db:"sequence_id" json:"sequence_id"`
LastCompletedStage int `db:"last_completed_stage" json:"last_completed_stage"`
LastCompletedSequence int `db:"last_completed_sequence" json:"last_completed_sequence"`
Workorder string `db:"workorder" json:"workorder"`
Product string `db:"product" json:"product"`
IsComplete string `db:"is_complete" json:"is_complete"`
IsScrapped string `db:"is_scrapped" json:"is_scrapped"`
ValueStream string `db:"value_stream" json:"value_stream"`
PromiseDate string `db:"promise_date" json:"promise_date"`
FailLock int `db:"fail_lock" json:"fail_lock"`
SequenceRev int `db:"sequence_rev" json:"sequence_rev"`
DateUpdated time.Time `db:"date_updated" json:"date_updated"`
Date time.Time `db:"date" json:"date"`
Time time.Time `db:"time" json:"time"`
Ptyp2 string `db:"ptyp2" json:"ptyp2"`
WoQty int `db:"wo_qty" json:"wo_qty"`
IsActive int `db:"is_active" json:"is_active"`
IsTimeLock int `db:"is_time_lock" json:"is_time_lock"`
TimeLockTimestamp time.Time `db:"time_lock_timestamp" json:"time_lock_timestamp"`
ScrapReason string `db:"scrap_reason" json:"scrap_reason"`
ScrappedBy int `db:"scrapped_by" json:"scrapped_by"`
}
seq_test_stage.go
package models
import (
"time"
)
type SeqTestStage struct {
ID int64 `gorm:"primary_key" db:"id" json:"id"`
PdcptypeId int `db:"pdcptype_id" json:"pdcptype"`
Description string `db:"description" json:"description"`
LongDescription string `db:"long_description" json:"long_description"`
IsActive int `db:"is_active" json:"is_active"`
DateAdded time.Time `db:"date_added" json:"date_added"`
DateUpdated time.Time `db:"date_updated" json:"date_updated"`
TimeLock int `db:"time_lock" json:"time_lock"`
LockMinutes int `db:"lock_minutes" json:"lock_minutes"`
LockHours int `db:"lock_hours" json:"lock_hours"`
}

Resources