Gorm: How to store a struct in a field - go

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),
})

Related

How to return nested entities after creating a new object?

Model Account contains nested structures - Currency and User
When I create a new instance of Account in DB, and then return it in my response, nested entities are empty:
type Account struct {
BaseModel
Name string `gorm:"size:64;not null" json:"name"`
Balance decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
UserID int `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
CurrencyID int `gorm:"not null" json:"-"`
Currency Currency `gorm:"foreignKey:CurrencyID" json:"currency"`
}
type CreateAccountBody struct {
Name string `json:"name" binding:"required"`
Balance decimal.Decimal `json:"balance"`
CurrencyID int `json:"currency_id" binding:"required"`
}
func CreateAccount(ctx *gin.Context) {
body := CreateAccountBody{}
if err := ctx.Bind(&body); err != nil {
log.Println("Error while binding body:", err)
ctx.JSON(
http.StatusBadRequest,
gin.H{"error": "Wrong request parameters"},
)
return
}
account := Account {
Name: body.Name,
Balance: body.Balance,
CurrencyID: body.CurrencyID,
UserID: 1,
}
if result := db.DB.Create(&account); result.Error != nil {
log.Println("Unable to create an account:", result.Error)
}
ctx.JSON(http.StatusCreated, gin.H{"data": account})
}
To avoid this problem, I refresh account variable with separate query:
db.DB.Create(&account)
db.DB.Preload("User").Preload("Currency").Find(&account, account.ID)
ctx.JSON(http.StatusCreated, gin.H{"data": account})
Is this the most effective and correct way to achieve the desired result?
I'm gonna share you how usually I managed this scenario. First, let me share the code.
main.go file
package main
import (
"context"
"gogindemo/handlers"
"github.com/gin-gonic/gin"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
db *gorm.DB
ctx *gin.Context
)
func init() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&handlers.Currency{})
db.AutoMigrate(&handlers.User{})
db.AutoMigrate(&handlers.Account{})
}
func AddDb() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "DB", db))
ctx.Next()
}
}
func main() {
db.Create(&handlers.User{Id: 1, Name: "john doe"})
db.Create(&handlers.User{Id: 2, Name: "mary hut"})
db.Create(&handlers.Currency{Id: 1, Name: "EUR"})
db.Create(&handlers.Currency{Id: 2, Name: "USD"})
r := gin.Default()
r.POST("/account", AddDb(), handlers.CreateAccount)
r.Run()
}
Here, I've just added the code for bootstrapping the database objects and add some dummy data to it.
handlers/handlers.go file
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
type User struct {
Id int
Name string
}
type Currency struct {
Id int
Name string
}
type Account struct {
Id int
Name string `gorm:"size:64;not null" json:"name"`
Balance decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
UserID int `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
CurrencyID int `gorm:"not null" json:"-"`
Currency Currency `gorm:"foreignKey:CurrencyID" json:"currency"`
}
type CreateAccountBody struct {
Name string `json:"name" binding:"required"`
Balance decimal.Decimal `json:"balance"`
CurrencyID int `json:"currency_id" binding:"required"`
}
func CreateAccount(c *gin.Context) {
db, ok := c.Request.Context().Value("DB").(*gorm.DB)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
var accountReq CreateAccountBody
if err := c.BindJSON(&accountReq); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "wrong request body payload"})
return
}
// create Account & update the "account" variable
account := Account{Name: accountReq.Name, Balance: accountReq.Balance, CurrencyID: accountReq.CurrencyID, UserID: 1}
db.Create(&account).Preload("Currency").Preload("User").Find(&account, account.Id)
c.IndentedJSON(http.StatusCreated, account)
}
Within this file, I actually talk with the database through the DB passed in the context. Now, back to your question.
If the relationship between the Currency/Account and User/Account is of type 1:1, then, you should rely on the Preload clause. This will load the related entity in a separate query instead of adding it in an INNER JOIN clause.
Let me know if this solves your issue, thanks!

insert hash coded data in gorm automatically

I have a table that represents my user data. There is a field that represents a phone number, and I want to store the hash of that automatically in the database at either update or insert.
so my model is :
type Users struct {
gorm.Model
ID uint `gorm:"autoIncrement;unique" json:"id"`
PhoneNumber string `json:"phone_number"`
HashID string `gorm:"primaryKey" json:"hash_id"`
Name string `gorm:"default:dear user" json:"name"`
Rank uint `json:"rank"`
Score uint `json:"score"`
Image string `json:"image"`
Email string `json:"email"`
Address string `json:"address"`
Birthday string `json:"birthday"`
Biography string `json:"biography"
}
How can I tell the GORM to fill the HashID column with the sha256 hash code of the PhoneNumber column while inserting or updating data?
You need something like this:
package main
import (
"crypto/sha256"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Users struct {
gorm.Model
Key string `json:"phone_number"`
Hash string `gorm:"primaryKey" json:"hash_id"`
}
func (u *Users) BeforeCreate(tx *gorm.DB) (err error) {
h := sha256.Sum256([]byte(u.Key))
u.Hash = fmt.Sprintf("%x", h[:])
return nil
}
func (u *Users) BeforeSave(tx *gorm.DB) (err error) {
h := sha256.Sum256([]byte(u.Key))
u.Hash = fmt.Sprintf("%x", h[:])
return nil
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Users{})
u := Users{Key: "123"}
db.Create(&u)
}
Check https://gorm.io/docs/index.html

How to call gorm alias properly?

Here is my code:
package main
import (
"fmt"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type ClientCustomer struct {
Id int `json:"Id"`
Name string
Created time.Time
key string
UserId int `gorm:"user_id"`
Modified time.Time
}
func (ClientCustomer) TableName() string {
return "Client_customer"
}
type ClientCustomerInvitation struct {
Id int
CustomerId int `gorm:"customer_id"`
CodeInvitationId int `gorm:"codeinvitation_id"`
}
func (ClientCustomerInvitation) TableName() string {
return "Client_customer_invitation"
}
func main() {
db, err := gorm.Open("sqlite3", "db.sqlite3?cache=shared&mode=rwc")
if err != nil {
panic("failed to connect database")
}
defer db.Close()
var clientCustomer ClientCustomer
rows, err := db.Model(&ClientCustomer{}).Rows()
defer rows.Close()
if err != nil {
panic(err)
}
var clientCustomerInvitation ClientCustomerInvitation
for rows.Next() {
db.ScanRows(rows, &clientCustomer)
db.First(&clientCustomerInvitation, "customer_id = ?", clientCustomer.Id)
fmt.Println(clientCustomer)
fmt.Println(clientCustomerInvitation)
}
}
but I'm not fond of this line:
db.First(&clientCustomerInvitation, "customer_id = ?", clientCustomer.Id)
Is there a way to call "customer_id" from the struct directly instead of using a string?
Ideally I would like to do something like:
db.First(&clientCustomerInvitation, ClientCustomerInvitation.CustomerId.gormAlias+" = ?", clientCustomer.Id)
I'm looking for a way to use the gorm alias for mapping the field in way that is more elegant and re usable than a mere string.
The only way to be able to get tag value from certain struct field, is by using reflect.
My suggestion, create a function that return tag value from specific struct field. Something like below:
func getGormAlias(obj interface{}, fieldName string) string {
if field, ok := reflect.TypeOf(obj).FieldByName(fieldName); ok {
return field.Tag.Get("gorm")
}
return ""
}
Then use it to get the tag value.
gormAliasCustomerId := getGormAlias(ClientCustomerInvitation{}, "CustomerId")
db.First(&clientCustomerInvitation, gormAliasCustomerId + " = ?", clientCustomer.Id)
Basically what getGormAlias() function does:
Use the reflect.Type on obj to get the reflect.Type value.
Then call .FieldByName() to get the reflect.Value object from selected field name.
The tag information is available through .Tag property. Use that to get the tag value of gorm.

Nested structs using gorm model

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}
}

Unable to create Associations via golang orm library

I've been trying to use the Associations feature in golang orm (https://github.com/jinzhu/gorm/), and am unable to create a pretty simple association.
In the example below, the user table contains data, but email table does not.
I've tried a bunch of things and I'm probably missing something basic, but have been unable to find the right answer in github/stackoverflow.
Code :
package main
import (
"database/sql"
"log"
"github.com/jinzhu/gorm"
"github.com/mattn/go-sqlite3"
)
var db gorm.DB
type User struct {
Name string
Mail Email
}
type Email struct {
Address string
}
//Initialize DB .
func InitDB() {
var DB_DRIVER string
sql.Register(DB_DRIVER, &sqlite3.SQLiteDriver{})
log.Printf("Initializing Database with ", DB_DRIVER)
dbSql, _ := sql.Open(DB_DRIVER, "simple-sqlite")
var err error
db, err = gorm.Open("sqlite3", dbSql)
if err != nil {
log.Fatalf("Got error when connecting to the database, the error is '%v'", err)
}
db.LogMode(true)
// Then you could invoke `*sql.DB`'s functions with it
db.DB().Ping()
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
// Disable table name's pluralization
db.SingularTable(true)
}
func InitSchema() {
db.CreateTable(&User{}, &Email{})
}
func DoStuff() {
user := User{Name: "Jinzhu", Mail: Email{Address: "hello#hello.com"}}
db.Create(&user)
}
func main() {
InitDB()
InitSchema()
DoStuff()
}
go run main.go prints the following output
2015/09/30 17:25:04 Initializing Database with %!(EXTRA string=)
[2015-09-30 17:25:04] [3.21ms] CREATE TABLE "user" ("name" varchar(255))
[2015-09-30 17:25:04] [4.01ms] CREATE TABLE "email" ("address" varchar(255) )
[2015-09-30 17:25:04] [0.54ms] INSERT INTO "user" ("name") VALUES ('Jinzhu')
Not sure what I'm missing here - appreciate any response!
You're missing your primary/foreign key references for each of your models, here's the updated code:
package main
import (
"database/sql"
"log"
"github.com/jinzhu/gorm"
"github.com/mattn/go-sqlite3"
)
var db gorm.DB
type User struct {
ID uint `gorm:"primary_key"`
Name string
Mail Email
MailID sql.NullInt64
}
type Email struct {
ID uint `gorm:"primary_key"`
Address string
}
//Initialize DB .
func InitDB() {
var DB_DRIVER string
sql.Register(DB_DRIVER, &sqlite3.SQLiteDriver{})
log.Printf("Initializing Database with ", DB_DRIVER)
dbSql, _ := sql.Open(DB_DRIVER, "simple-sqlite")
var err error
db, err = gorm.Open("sqlite3", dbSql)
if err != nil {
log.Fatalf("Got error when connecting to the database, the error is '%v'", err)
}
db.LogMode(true)
// Then you could invoke `*sql.DB`'s functions with it
db.DB().Ping()
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
// Disable table name's pluralization
db.SingularTable(true)
}
func InitSchema() {
db.CreateTable(&User{}, &Email{})
}
func DoStuff() {
user := User{Name: "Jinzhu", Mail: Email{Address: "hello#hello.com"}}
db.Create(&user)
}
func main() {
InitDB()
InitSchema()
DoStuff()
}
notice the primary keys on the User and Email structs as well as the foreign key reference on User

Resources