2020-05-31 09:18:07 +03:00
|
|
|
package tests_test
|
|
|
|
|
|
|
|
import (
|
2020-06-18 04:15:23 +03:00
|
|
|
"database/sql/driver"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2023-05-15 04:59:26 +03:00
|
|
|
"reflect"
|
2020-05-31 09:18:07 +03:00
|
|
|
"testing"
|
2023-05-15 04:59:26 +03:00
|
|
|
"time"
|
2020-05-31 09:18:07 +03:00
|
|
|
|
2020-06-02 04:16:07 +03:00
|
|
|
"gorm.io/gorm"
|
2020-08-04 07:10:19 +03:00
|
|
|
. "gorm.io/gorm/utils/tests"
|
2020-05-31 09:18:07 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestEmbeddedStruct(t *testing.T) {
|
2020-06-23 04:09:46 +03:00
|
|
|
type ReadOnly struct {
|
|
|
|
ReadOnly *bool
|
|
|
|
}
|
|
|
|
|
2020-05-31 09:18:07 +03:00
|
|
|
type BasePost struct {
|
|
|
|
Id int64
|
|
|
|
Title string
|
|
|
|
URL string
|
2020-06-23 04:09:46 +03:00
|
|
|
ReadOnly
|
2020-05-31 09:18:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type Author struct {
|
|
|
|
ID string
|
|
|
|
Name string
|
|
|
|
Email string
|
|
|
|
}
|
|
|
|
|
|
|
|
type HNPost struct {
|
|
|
|
BasePost
|
|
|
|
Author `gorm:"EmbeddedPrefix:user_"` // Embedded struct
|
|
|
|
Upvotes int32
|
|
|
|
}
|
|
|
|
|
|
|
|
type EngadgetPost struct {
|
|
|
|
BasePost BasePost `gorm:"Embedded"`
|
2022-12-19 06:49:05 +03:00
|
|
|
Author *Author `gorm:"Embedded;EmbeddedPrefix:author_"` // Embedded struct
|
2020-05-31 09:18:07 +03:00
|
|
|
ImageUrl string
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Migrator().DropTable(&HNPost{}, &EngadgetPost{})
|
|
|
|
if err := DB.Migrator().AutoMigrate(&HNPost{}, &EngadgetPost{}); err != nil {
|
|
|
|
t.Fatalf("failed to auto migrate, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, name := range []string{"author_id", "author_name", "author_email"} {
|
|
|
|
if !DB.Migrator().HasColumn(&EngadgetPost{}, name) {
|
|
|
|
t.Errorf("should has prefixed column %v", name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt := gorm.Statement{DB: DB}
|
|
|
|
if err := stmt.Parse(&EngadgetPost{}); err != nil {
|
|
|
|
t.Fatalf("failed to parse embedded struct")
|
|
|
|
} else if len(stmt.Schema.PrimaryFields) != 1 {
|
|
|
|
t.Errorf("should have only one primary field with embedded struct, but got %v", len(stmt.Schema.PrimaryFields))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, name := range []string{"user_id", "user_name", "user_email"} {
|
|
|
|
if !DB.Migrator().HasColumn(&HNPost{}, name) {
|
|
|
|
t.Errorf("should has prefixed column %v", name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// save embedded struct
|
|
|
|
DB.Save(&HNPost{BasePost: BasePost{Title: "news"}})
|
|
|
|
DB.Save(&HNPost{BasePost: BasePost{Title: "hn_news"}})
|
|
|
|
var news HNPost
|
|
|
|
if err := DB.First(&news, "title = ?", "hn_news").Error; err != nil {
|
|
|
|
t.Errorf("no error should happen when query with embedded struct, but got %v", err)
|
|
|
|
} else if news.Title != "hn_news" {
|
|
|
|
t.Errorf("embedded struct's value should be scanned correctly")
|
|
|
|
}
|
|
|
|
|
2022-12-19 06:49:05 +03:00
|
|
|
DB.Save(&EngadgetPost{BasePost: BasePost{Title: "engadget_news"}, Author: &Author{Name: "Edward"}})
|
|
|
|
DB.Save(&EngadgetPost{BasePost: BasePost{Title: "engadget_article"}, Author: &Author{Name: "George"}})
|
2020-05-31 09:18:07 +03:00
|
|
|
var egNews EngadgetPost
|
|
|
|
if err := DB.First(&egNews, "title = ?", "engadget_news").Error; err != nil {
|
|
|
|
t.Errorf("no error should happen when query with embedded struct, but got %v", err)
|
|
|
|
} else if egNews.BasePost.Title != "engadget_news" {
|
|
|
|
t.Errorf("embedded struct's value should be scanned correctly")
|
|
|
|
}
|
2022-12-19 06:49:05 +03:00
|
|
|
|
|
|
|
var egPosts []EngadgetPost
|
|
|
|
if err := DB.Order("author_name asc").Find(&egPosts).Error; err != nil {
|
|
|
|
t.Fatalf("no error should happen when query with embedded struct, but got %v", err)
|
|
|
|
}
|
|
|
|
expectAuthors := []string{"Edward", "George"}
|
|
|
|
for i, post := range egPosts {
|
|
|
|
t.Log(i, post.Author)
|
|
|
|
if want := expectAuthors[i]; post.Author.Name != want {
|
|
|
|
t.Errorf("expected author %s got %s", want, post.Author.Name)
|
|
|
|
}
|
|
|
|
}
|
2020-05-31 09:18:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEmbeddedPointerTypeStruct(t *testing.T) {
|
|
|
|
type BasePost struct {
|
|
|
|
Id int64
|
|
|
|
Title string
|
|
|
|
URL string
|
|
|
|
}
|
|
|
|
|
2023-04-11 05:13:25 +03:00
|
|
|
type Author struct {
|
2023-05-15 04:59:26 +03:00
|
|
|
ID string
|
|
|
|
Name string
|
|
|
|
Email string
|
|
|
|
Age int
|
|
|
|
Content Content
|
|
|
|
ContentPtr *Content
|
|
|
|
Birthday time.Time
|
|
|
|
BirthdayPtr *time.Time
|
2023-04-11 05:13:25 +03:00
|
|
|
}
|
|
|
|
|
2020-05-31 09:18:07 +03:00
|
|
|
type HNPost struct {
|
|
|
|
*BasePost
|
|
|
|
Upvotes int32
|
2023-04-11 05:13:25 +03:00
|
|
|
*Author `gorm:"EmbeddedPrefix:user_"` // Embedded struct
|
2020-05-31 09:18:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
DB.Migrator().DropTable(&HNPost{})
|
|
|
|
if err := DB.Migrator().AutoMigrate(&HNPost{}); err != nil {
|
|
|
|
t.Fatalf("failed to auto migrate, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Create(&HNPost{BasePost: &BasePost{Title: "embedded_pointer_type"}})
|
|
|
|
|
|
|
|
var hnPost HNPost
|
|
|
|
if err := DB.First(&hnPost, "title = ?", "embedded_pointer_type").Error; err != nil {
|
|
|
|
t.Errorf("No error should happen when find embedded pointer type, but got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if hnPost.Title != "embedded_pointer_type" {
|
|
|
|
t.Errorf("Should find correct value for embedded pointer type")
|
|
|
|
}
|
2023-04-11 05:13:25 +03:00
|
|
|
|
|
|
|
if hnPost.Author != nil {
|
|
|
|
t.Errorf("Expected to get back a nil Author but got: %v", hnPost.Author)
|
|
|
|
}
|
2023-05-15 04:59:26 +03:00
|
|
|
|
|
|
|
now := time.Now().Round(time.Second)
|
|
|
|
NewPost := HNPost{
|
|
|
|
BasePost: &BasePost{Title: "embedded_pointer_type2"},
|
|
|
|
Author: &Author{
|
|
|
|
Name: "test",
|
|
|
|
Content: Content{"test"},
|
|
|
|
ContentPtr: nil,
|
|
|
|
Birthday: now,
|
|
|
|
BirthdayPtr: nil,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
DB.Create(&NewPost)
|
|
|
|
|
|
|
|
hnPost = HNPost{}
|
|
|
|
if err := DB.First(&hnPost, "title = ?", NewPost.Title).Error; err != nil {
|
|
|
|
t.Errorf("No error should happen when find embedded pointer type, but got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if hnPost.Title != NewPost.Title {
|
|
|
|
t.Errorf("Should find correct value for embedded pointer type")
|
|
|
|
}
|
|
|
|
|
|
|
|
if hnPost.Author.Name != NewPost.Author.Name {
|
|
|
|
t.Errorf("Expected to get Author name %v but got: %v", NewPost.Author.Name, hnPost.Author.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(NewPost.Author.Content, hnPost.Author.Content) {
|
|
|
|
t.Errorf("Expected to get Author content %v but got: %v", NewPost.Author.Content, hnPost.Author.Content)
|
|
|
|
}
|
|
|
|
|
|
|
|
if hnPost.Author.ContentPtr != nil {
|
|
|
|
t.Errorf("Expected to get nil Author contentPtr but got: %v", hnPost.Author.ContentPtr)
|
|
|
|
}
|
|
|
|
|
|
|
|
if NewPost.Author.Birthday.UnixMilli() != hnPost.Author.Birthday.UnixMilli() {
|
|
|
|
t.Errorf("Expected to get Author birthday with %+v but got: %+v", NewPost.Author.Birthday, hnPost.Author.Birthday)
|
|
|
|
}
|
|
|
|
|
|
|
|
if hnPost.Author.BirthdayPtr != nil {
|
|
|
|
t.Errorf("Expected to get nil Author birthdayPtr but got: %+v", hnPost.Author.BirthdayPtr)
|
|
|
|
}
|
2020-05-31 09:18:07 +03:00
|
|
|
}
|
2020-06-18 04:15:23 +03:00
|
|
|
|
|
|
|
type Content struct {
|
2020-06-19 17:51:46 +03:00
|
|
|
Content interface{} `gorm:"type:String"`
|
2020-06-18 04:15:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c Content) Value() (driver.Value, error) {
|
2023-05-15 04:59:26 +03:00
|
|
|
// mssql driver with issue on handling null bytes https://github.com/denisenkom/go-mssqldb/issues/530,
|
|
|
|
b, err := json.Marshal(c)
|
|
|
|
return string(b[:]), err
|
2020-06-18 04:15:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Content) Scan(src interface{}) error {
|
|
|
|
var value Content
|
2023-05-15 04:59:26 +03:00
|
|
|
str, ok := src.(string)
|
|
|
|
if !ok {
|
|
|
|
byt, ok := src.([]byte)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Embedded.Scan byte assertion failed")
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(byt, &value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := json.Unmarshal([]byte(str), &value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-06-18 04:15:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
*c = value
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEmbeddedScanValuer(t *testing.T) {
|
|
|
|
type HNPost struct {
|
|
|
|
gorm.Model
|
|
|
|
Content
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Migrator().DropTable(&HNPost{})
|
|
|
|
if err := DB.Migrator().AutoMigrate(&HNPost{}); err != nil {
|
|
|
|
t.Fatalf("failed to auto migrate, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
hnPost := HNPost{Content: Content{Content: "hello world"}}
|
|
|
|
|
|
|
|
if err := DB.Create(&hnPost).Error; err != nil {
|
|
|
|
t.Errorf("Failed to create got error %v", err)
|
|
|
|
}
|
|
|
|
}
|
2020-08-04 07:10:19 +03:00
|
|
|
|
|
|
|
func TestEmbeddedRelations(t *testing.T) {
|
|
|
|
type AdvancedUser struct {
|
|
|
|
User `gorm:"embedded"`
|
|
|
|
Advanced bool
|
|
|
|
}
|
|
|
|
|
2020-08-18 06:21:40 +03:00
|
|
|
DB.Migrator().DropTable(&AdvancedUser{})
|
2020-08-04 07:10:19 +03:00
|
|
|
|
2020-08-18 06:21:40 +03:00
|
|
|
if err := DB.AutoMigrate(&AdvancedUser{}); err != nil {
|
2020-09-08 13:24:35 +03:00
|
|
|
if DB.Dialector.Name() != "sqlite" {
|
|
|
|
t.Errorf("Failed to auto migrate advanced user, got error %v", err)
|
|
|
|
}
|
2020-08-04 07:10:19 +03:00
|
|
|
}
|
|
|
|
}
|
2022-07-25 09:10:30 +03:00
|
|
|
|
|
|
|
func TestEmbeddedTagSetting(t *testing.T) {
|
|
|
|
type Tag1 struct {
|
|
|
|
Id int64 `gorm:"autoIncrement"`
|
|
|
|
}
|
|
|
|
type Tag2 struct {
|
|
|
|
Id int64
|
|
|
|
}
|
|
|
|
|
|
|
|
type EmbeddedTag struct {
|
|
|
|
Tag1 Tag1 `gorm:"Embedded;"`
|
|
|
|
Tag2 Tag2 `gorm:"Embedded;EmbeddedPrefix:t2_"`
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Migrator().DropTable(&EmbeddedTag{})
|
|
|
|
err := DB.Migrator().AutoMigrate(&EmbeddedTag{})
|
|
|
|
AssertEqual(t, err, nil)
|
|
|
|
|
|
|
|
t1 := EmbeddedTag{Name: "embedded_tag"}
|
|
|
|
err = DB.Save(&t1).Error
|
|
|
|
AssertEqual(t, err, nil)
|
|
|
|
if t1.Tag1.Id == 0 {
|
|
|
|
t.Errorf("embedded struct's primary field should be rewrited")
|
|
|
|
}
|
|
|
|
}
|