GORM Cannot pass foreign key to query - go

I am usin jinzhu GORM package for connecting to the DB, etc.
This is my code
package pizzas
import (
"github.com/jinzhu/gorm"
"github.com/gin-gonic/gin"
"speedy-gonzales/db"
"net/http"
)
type Pizza struct {
gorm.Model
Name string `gorm:"not null"`
Image string `sql:"type:text"`
PSizesAndPrices []PizzaPriceSize
}
type PizzaPriceSize struct {
gorm.Model
SizeTitle string `gorm:"column:size_title;type:varchar(50);not null'"`
PriceEur int `gorm:"column:price_eur"`
PriceBam int `gorm:"column:price_bam"`
PizzaID uint `gorm:"index"`
}
func (PizzaPriceSize) TableName() string {
return "pizza_price_sizes"
}
func FetchPizzasWithSizes(c *gin.Context) {
var pizza_model []Pizza
var p_sizes_prices_model []PizzaPriceSize
dB := db.DbConnect()
dB.Debug().Model(&pizza_model).Related(&p_sizes_prices_model)
c.JSON(http.StatusOK, gin.H{
"status": http.StatusOK,
"message": p_sizes_prices_model,
})
return
}
When I run the app this is what I get from debug console:
SELECT * FROM pizza_price_sizes WHERE pizza_price_sizes.deleted_at IS NULL AND ((pizza_id = '0'))
My question is how can I pass pizza_id to this query?

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!

Gorm: How to store a struct in a field

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

Gorm BeforeCreate hook is not working to generate a UUID

I am trying to generate a UUID every time I create a company. I thought about doing it in the hook function, but it is not working. I tried to panic the program if it is executed, but the hook is not responding.
I followed the documentation on how I can implement the hook, but it does not work for me.
Hooks documentation: https://gorm.io/docs/hooks.html
import (
"time"
"github.com/jinzhu/gorm"
uuid "github.com/satori/go.uuid"
)
type Base struct {
ID uuid.UUID `gorm:"type:UUID;" json:"_id"`
CreatedAt time.Time `gorm:"type:Date;" json:"created_at"`
UpdatedAt time.Time `gorm:"type:Date;" json:"updated_at"`
DeletedAt *time.Time `gorm:"type:Date;" json:"deleted_at"`
}
type CompanyModel struct {
Base
// Departments []DepartmentModel `gorm:"foreignKey:id" json:"departments"`
Cvr int `gorm:"not null" json:"cvr"`
Name string `gorm:"not null" json:"name"`
}
// BeforeCreate creates
func (u *Base) BeforeCreate(tx *gorm.DB) (err error) {
u.ID = uuid.NewV4()
panic(u.ID)
return
}
// BeforeSave creates
func (u *Base) BeforeSave(tx *gorm.DB) (err error) {
u.ID = uuid.NewV4()
panic(u.ID)
return
}
I am creating a company by the following
tx := db.Session(&gorm.Session{SkipHooks: false}).Create(&models.CompanyModel{
Cvr: 12333,
Name: "test,
// Departments: company.Departments,
},
)
The saved value when I execute is always:
[1.479ms] [rows:0] INSERT INTO "company_models" ("id","created_at","updated_at","deleted_at","cvr","name") VALUES ('00000000-0000-0000-0000-000000000000','2021-04-21 18:00:51.879','2021-04-21 18:00:51.879',NULL,12333,'test')
It looks like you are using gorm v1, and I think you need gorm v2. The import is "gorm.io/gorm".
Minimum working example:
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
Id int
Name string
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.Name = "Steve"
return
}
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
u.Name = "Sally"
return
}
func main() {
db, err := gorm.Open(sqlite.Open("hooks.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&User{})
user := User{
Name: "Blake",
}
db.Create(&user)
fmt.Println(user.Name)
user.Name = "Tyler"
db.Save(&user)
fmt.Println(user.Name)
}
Prints:
Steve
Sally

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

Golang declaring variables as NULLs

As per the example below, I need to get email and firstname and dateofbirth and so on as NullStrings and NullTimes as required, as my User struct is using them. How do I declare variables as NULLs
package entities
import (
"database/sql"
"github.com/go-sql-driver/mysql"
"testing"
"time"
)
var (
email = sql.NullString("mail#gmail.com") << Does not work
hashedPassword = "password"
firstName = "Lee"
lastName = "Brooks"
dateOfBirth = time.Now
height = 1.85
weight = 101.3
)
func privacyConcernedUser() *User {
return &User{
Email: email, << These all complain eg: cannot use Email (type string) as type sql.NullString in field value
HashedPassword: hashedPassword,
FirstName: firstName,
LastName: lastName,
}
}
sql.NullString isn't a drop-in replacement for the string type, you have to some work with it.
package main
import "database/sql"
import "fmt"
type User struct {
Name string
}
func main() {
var nsName sql.NullString
if err := nsName.Scan("User's Name"); err != nil {
panic(err)
}
user := &User{Name: nsName.String}
fmt.Println(user)
}
You can check if the NullString is valid with nsName.Valid.
http://golang.org/pkg/database/sql/#NullString
sql.NullString("mail#gmail.com") << Does not work
Try:
sql.NullString{"mail#gmail.com", true}
see http://golang.org/pkg/database/sql/#NullString

Resources