How to join two tables in GORM - go

I have two tables,
SET search_path = public;
CREATE TABLE IF NOT EXISTS changelog
(
id BIGINT NOT NULL PRIMARY KEY,
object_type TEXT,
object_id BIGINT,
parent_type TEXT,
parent_id BIGINT,
action TEXT,
field TEXT,
old_value TEXT,
new_value TEXT,
comment_id INTEGER,
created_on TIMESTAMP WITHOUT TIME ZONE,
created_by BIGINT
);
CREATE TABLE IF NOT EXISTS changelog_comments
(
id INTEGER NOT NULL PRIMARY KEY,
comment TEXT,
created_on TIMESTAMP WITHOUT TIME ZONE,
created_by BIGINT
);
SET search_path = DEFAULT;
I want to implement a search method for the changelog which returns the fields
"objectType"
"objectId"
"parentType"
"parentId"
"action"
"field"
"oldValue"
"newValue"
"comment"
"createdBy"
"createdOn"
as you can see the result comes from the join of the two tables.
I found https://gorm.io/docs/preload.html but to be honest, didn't get that how can I achieve what I need.
I thought something like the following could be helpful
type ChangelogResponseItem struct {
ObjectType string `json:"objectType"`
ObjectID uuid.UUID `json:"objectId"`
ParentType string `json:"parentType"`
ParentID uuid.UUID `json:"parentId"`
Action string `json:"action"`
Field *string `json:"field"`
OldValue *string `json:"oldValue"`
NewValue *string `json:"newValue"`
Comment *string `json:"comment"`
CreatedBy *uint64 `json:"createdBy"`
CreatedOn *time.Time `json:"createdOn"`
}
The question is that how can get what I mentioned from the mentioned tables in GORM?

One way to do it would be to combine Joins and Select methods to get what you want. Based on your table, it would look something like this:
list := []ChangelogResponseItem{}
tx := db.Table("changelog").
Joins("INNER JOIN changelog_comments cc ON cc.id = changelog.comment_id").
Select("changelog.objectType, changelog.object_type, changelog.object_id, changelog.parent_type, changelog.parent_id, changelog.action, changelog.field, changelog.old_value, changelog.new_value, cc.comment, changelog.created_on, changelog.created_by").
Find(&list)
if tx.Error != nil {
// handle error
}
This is just to return the data, a search would include additional Where methods.
EDIT:
Solution with a preload option:
Structs:
type ChangelogComment struct {
ID uint64 `json:"id"`
Comment string `json:"comment"`
}
type Changelog struct {
ObjectType string `json:"objectType"`
ObjectID uuid.UUID `json:"objectId"`
ParentType string `json:"parentType"`
ParentID uuid.UUID `json:"parentId"`
Action string `json:"action"`
Field *string `json:"field"`
OldValue *string `json:"oldValue"`
NewValue *string `json:"newValue"`
CommentID uint64 `json:"comment_id"`
Comment *ChangelogComment `json:"comment"`
CreatedBy *uint64 `json:"createdBy"`
CreatedOn *time.Time `json:"createdOn"`
}
Code with the Preload method:
list := []Changelog{}
tx := db.Preload("Comment").Find(&list)
if tx.Error != nil {
// handle error
}
Please note that in this case, you will have a different JSON object, the structure od the object will not be flat, because you will have a comment field as well.

Related

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?

A foreign key constraint fails when insert to table with gorm

When I try to insert to a table with gorm which has one to many relationship, I get this error:
Error 1452: Cannot add or update a child row: a foreign key constraint fails (`Todo`.`todos`, CONSTRAINT `fk_users_todo_list` FOREIGN KEY (`fk_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE)....
These are my models:
type Base struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `gorm:"index"`
}
type User struct {
Base
FirstName string `form:"first_name" gorm:"type:varchar(20)"`
LastName string `form:"last_name" gorm:"type:varchar(20)"`
UserName string `form:"user_name" gorm:"unique;type:varchar(20)"`
Email string `form:"email" gorm:"type:varchar(100)"`
Password string `form:"password" gorm:"type:varchar(30)"`
Gender types.UserGender `form:"gender" gorm:"type:smallint"`
TodoList []Todo `gorm:"foreignKey:FkID;constraint:onDelete:SET NULL,onUpdate:CASCADE" json:"omitempty"`
}
type Todo struct {
Base
Name string
Description string
Status types.TaskStatus
FkID uint
}
And this is the function that I wrote:
func (d *DB) AddTODO(todo *models.Todo, userName string) error {
user := &models.User{UserName: userName}
err := d.db.Model(&user).Where("user_name = ?", userName).Association("TodoList").
Append([]models.Todo{*todo})
if err != nil {
return err
}
return nil
}
I'm using MariaDB.
It want user from gorm result. Change your method maybe like this:
// arg user is from gorm result somewhere
func (d *DB) AddTODO(user *models.User, todo *models.Todo) error {
return d.db.Model(user).Association("TodoList").Append([]models.Todo{*todo})
}
TodoList lack references keyword. It should be like this.
type User struct {
Base
FirstName string `form:"first_name" gorm:"type:varchar(20)"`
LastName string `form:"last_name" gorm:"type:varchar(20)"`
UserName string `form:"user_name" gorm:"unique;type:varchar(20)"`
Email string `form:"email" gorm:"type:varchar(100)"`
Password string `form:"password" gorm:"type:varchar(30)"`
Gender types.UserGender `form:"gender" gorm:"type:smallint"`
TodoList []Todo `gorm:"foreignKey:FkID;references:ID;constraint:onDelete:SET NULL,onUpdate:CASCADE" json:"omitempty"`
}

Beego, unable to query many to many relation field

I have the following models:
type User struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
FirstName *string `json:"first_name"`
LastName *string `json:"last_name" orm:"null"`
FullName *string `json:"full_name"`
Posts []*Post `orm:"rel(m2m);rel_through(demo/db/models.UserPosts)" json:"posts"`
}
and
type UserPosts struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
Post *Post `orm:"rel(fk);column(post_id)" json:"post"`
User *User `orm:"rel(fk);column(users_id)" json:"user"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
IsInactive bool `json:"is_inactive"`
}
and
type Post struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
Content *string `json:"content"`
Type *string `json:"type" orm:"null"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
IsInactive bool `json:"is_inactive"`
Users []*User `orm:"reverse(many)" json:"users"`
}
I want to order the distinct users queried via the created_at field of the Post table.
But whenever I run the following query
import "github.com/astaxie/beego/orm"
db := orm.NewOrm()
qs := db.QueryTable(new(models.User))
num, err := qs.Distinct().RelatedSel().All(&users)
The query formed by beego does not contain the field created_at of Post table and hence it gives an error. The query for the same is listed below:
SELECT DISTINCT T0."id", T0."first_name", T0."last_name", T0."full_name"
FROM "users" T0
INNER JOIN "user_posts" T1
ON T1."users_id" = T0."id"
WHERE T1."tenants_id"
IN $1
AND T1."is_inactive" = $2
ORDER BY T1."created_at" DESC ;
I checked through the documentation but wasn't able to find any hint to the solution.
NOTE: The disctinct is required due to other constraints I have in my code which I have not highlighted in the question since that is out of scope
Hoping someone can point me in the right direction to achieve the same.

How to define foreign key in GORM library model referencing to another table id?

I have these three models
type Store struct {
Id int `gorm: "type:int;primary_key;AUTO_INCREMENT;NOT NULL;UNIQUE"`
Store_Id string `gorm:"type:varchar(190)";"NOT NULL";"UNIQUE"`
}
type File struct {
Id int `gorm: "type:int;primary_key;AUTO_INCREMENT;NOT NULL;UNIQUE"`
Organization_Id string `gorm:"type:varchar(190)";"NOT NULL"`
File_Id string `gorm:"type:varchar(190)";"NOT NULL";"UNIQUE"`
}
type File_Store_Linker struct {
Id int `gorm: "type:int;primary_key;AUTO_INCREMENT;NOT NULL"`
File_Id string `gorm:"type:varchar(190)";"NOT NULL"`
Store_Id string `gorm:"type:varchar(190)";"NOT NULL"`
File_Type string `gorm:"type:varchar(50)";"NOT NULL"`
}
I want to create foreign keys like this in the File_Storage_Linker struct.
create table file_store_linker (
id INT NOT NULL AUTO_INCREMENT,
file_id VARCHAR(190) NOT NULL,
store_id VARCHAR(190) NOT NULL,
file_type VARCHAR(50) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (file_id)
REFERENCES file (file_id),
FOREIGN KEY (store_id)
REFERENCES storage (store_id)
);
How can it be done?
I found the solution to it. Actually I added foreign keys externally like this. So you need to create or migrate the tables first and then add the foreign keys.
type Store struct {
Id int `gorm: "type:int;primary_key;AUTO_INCREMENT;NOT NULL;UNIQUE"`
Store_Id string `gorm:"type:varchar(190)";"NOT NULL";"UNIQUE"`
}
type File struct {
Id int `gorm: "type:int;primary_key;AUTO_INCREMENT;NOT NULL;UNIQUE"`
Organization_Id string `gorm:"type:varchar(190)";"NOT NULL"`
File_Id string `gorm:"type:varchar(190)";"NOT NULL";"UNIQUE"`
}
type File_Store_Linker struct {
Id int `gorm: "type:int;primary_key;AUTO_INCREMENT;NOT NULL"`
File_Id string `gorm:"type:varchar(190)";"NOT NULL"`
Store_Id string `gorm:"type:varchar(190)";"NOT NULL"`
File_Type string `gorm:"type:varchar(50)";"NOT NULL"`
}
func AddForeignKeys(gormDb *gorm.DB) {
gormDb.Model(&File_Store_Linker{}).AddForeignKey("file_id","file(file_id)",
"RESTRICT","RESTRICT")
gormDb.Model(&File_Store_Linker{}).AddForeignKey("store_id","store(store_id)","
RESTRICT","RESTRICT")
}

"Has many" relation giving empty result

I am trying to define one to many relation with gorm ORM . I have read all the docs over and over again . Could not find a way to do it.
func GetUser1(c *gin.Context) {
var user models.User
var activities models.UserActivity
query := DB.Debug().Find(&user, 1).Model(&user).Related(&activities).Error
if query != nil {
panic(query)
}
c.JSON(200, &user)
}
My Models are ..
type User struct {
Id int64
Username string
Password string `json:"-"`
Email string `json:",omitempty"`
UserActivities []UserActivity
}
type UserActivity struct {
Id int64
UserId int64 `json:"-"`
ActorId int64
CreatedAt time.Time
}
Debug Results are
[2015-11-21 22:21:54] [3.17ms] SELECT * FROM `users` WHERE (`id` = '1')
[2015-11-21 22:21:54] [1.39ms] SELECT * FROM `user_activities` WHERE (`user_id` = '1')
But I am getting null results
{
"Id": 1,
"Username": "test1",
"Email": "test1#friesen.com",
"UserActivities": null
}
All the primary keys and Indexes are right . I have also tried puttin gorm:"primary_key" and sql:"index" in UserActivities no luck so far .
However if I replace UserActivities []UserActivity with UserActivities UserActivity then i get only one row which seems to be right but why UserActivities []UserActivity giving no results
use gorm built in model gorm.Model so that your not messing up the "ID" constant
The solution to your problem is very simple.
Rather than querying
var user models.User
var activities models.UserActivity
query := DB.Debug().Find(&user, 1).Model(&user).Related(&activities).Error
just query
var user models.User
query := DB.Debug().Find(&user, 1).Model(&user).Related(&user.UserActivities).Error

Resources