I'm trying to learn Go and Gorm by building a little prototype order management app. The database is MySQL. With simple queries Gorm has been stellar. However, when trying to obtain a result set involving a combination one-to-many with a has-one relationship Gorm seems to fall short. No doubt, it is my lack of understanding that is actually falling short. I can't seem to find any online examples of what I am trying to accomplish. Any help would be greatly appreciated.
Go Structs
// Order
type Order struct {
gorm.Model
Status string
OrderItems []OrderItem
}
// Order line item
type OrderItem struct {
gorm.Model
OrderID uint
ItemID uint
Item Item
Quantity int
}
// Product
type Item struct {
gorm.Model
ItemName string
Amount float32
}
Database tables
orders
id | status
1 | pending
order_items
id | order_id | item_id | quantity
1 | 1 | 1 | 1
2 | 1 | 2 | 4
items
id | item_name | amount
1 | Go Mug | 12.49
2 | Go Keychain | 6.95
3 | Go T-Shirt | 17.99
Current query
order := &Order
if err := db.Where("id = ? and status = ?", reqOrder.id, "pending")
.First(&order).Error; err != nil {
fmt.Printf(err.Error())
}
db.Model(&order).Association("OrderItems").Find(&order.OrderItems)
Results (gorm makes 2 db queries)
order == Order {
id: 1,
status: pending,
OrderItems[]: {
{
ID: 1,
OrderID: 1,
ItemID: 1,
Item: nil,
Quantity: 1,
},
{
ID: 2,
OrderID: 1,
ItemID: 2,
Item: nil,
Quantity: 4,
}
}
Alternative query
order := &Order
db.Where("id = ? and status = ?", reqOrder.id, "cart")
.Preload("OrderItems").Preload("OrderItems.Item").First(&order)
Results (gorm makes 3 db queries)
order == Order {
id: 1,
status: pending,
OrderItems[]: {
{
ID: 1,
OrderID: 1,
ItemID: 1,
Item: {
ID: 1,
ItemName: Go Mug,
Amount: 12.49,
}
Quantity: 1,
},
{
ID: 2,
OrderID: 1,
ItemID: 2,
Item: {
ID: 2,
ItemName: Go Keychain,
Amount: 6.95,
},
Quantity: 4,
}
}
Ideal results
The "Alternative query" above produces the ideal query results. However, Gorm makes 3 separate database queries to do so. Ideally, the same results would be accomplished with 1 (or 2) database queries.
This could be accomplished in MySQL with a couple of joins. Gorm allows for joins. But, I was hoping to take advantage of some of Gorm's relational magic.
Thanks a bunch!
As described in this issue, gorm is not designed to use joins to preload other structs values. If you would like to continue to use gorm and have the ability to use joins to load values, one must use the SQL Builder exposed in gorm, and write some code to scan the desired values.
This would become burdensome if there are numerous tables that have to be accounted for.
If xorm is available as an option, they support loading struct values. Described under the find bullet point, here.
Note: I did not scan all the fields, just enough to get the point across.
EXAMPLE:
package main
import (
"log"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/kylelemons/godebug/pretty"
)
// Order
type Order struct {
gorm.Model
Status string
OrderItems []OrderItem
}
// Order line item
type OrderItem struct {
gorm.Model
OrderID uint
ItemID uint
Item Item
Quantity int
}
// Product
type Item struct {
gorm.Model
ItemName string
Amount float32
}
var (
items = []Item{
{ItemName: "Go Mug", Amount: 12.49},
{ItemName: "Go Keychain", Amount: 6.95},
{ItemName: "Go Tshirt", Amount: 17.99},
}
)
func main() {
db, err := gorm.Open("sqlite3", "/tmp/gorm.db")
db.LogMode(true)
if err != nil {
log.Panic(err)
}
defer db.Close()
// Migrate the schema
db.AutoMigrate(&OrderItem{}, &Order{}, &Item{})
// Create Items
for index := range items {
db.Create(&items[index])
}
order := Order{Status: "pending"}
db.Create(&order)
item1 := OrderItem{OrderID: order.ID, ItemID: items[0].ID, Quantity: 1}
item2 := OrderItem{OrderID: order.ID, ItemID: items[1].ID, Quantity: 4}
db.Create(&item1)
db.Create(&item2)
// Query with joins
rows, err := db.Table("orders").Where("orders.id = ? and status = ?", order.ID, "pending").
Joins("Join order_items on order_items.order_id = orders.id").
Joins("Join items on items.id = order_items.id").
Select("orders.id, orders.status, order_items.order_id, order_items.item_id, order_items.quantity" +
", items.item_name, items.amount").Rows()
if err != nil {
log.Panic(err)
}
defer rows.Close()
// Values to load into
newOrder := &Order{}
newOrder.OrderItems = make([]OrderItem, 0)
for rows.Next() {
orderItem := OrderItem{}
item := Item{}
err = rows.Scan(&newOrder.ID, &newOrder.Status, &orderItem.OrderID, &orderItem.ItemID, &orderItem.Quantity, &item.ItemName, &item.Amount)
if err != nil {
log.Panic(err)
}
orderItem.Item = item
newOrder.OrderItems = append(newOrder.OrderItems, orderItem)
}
log.Print(pretty.Sprint(newOrder))
}
Output:
/tmp/main.go.go:55)
[2018-06-18 18:33:59] [0.74ms] INSERT INTO "items" ("created_at","updated_at","deleted_at","item_name","amount") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'Go Mug','12.49')
[1 rows affected or returned ]
(/tmp/main.go.go:55)
[2018-06-18 18:33:59] [0.50ms] INSERT INTO "items" ("created_at","updated_at","deleted_at","item_name","amount") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'Go Keychain','6.95')
[1 rows affected or returned ]
(/tmp/main.go.go:55)
[2018-06-18 18:33:59] [0.65ms] INSERT INTO "items" ("created_at","updated_at","deleted_at","item_name","amount") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'Go Tshirt','17.99')
[1 rows affected or returned ]
(/tmp/main.go.go:58)
[2018-06-18 18:33:59] [0.71ms] INSERT INTO "orders" ("created_at","updated_at","deleted_at","status") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'pending')
[1 rows affected or returned ]
(/tmp/main.go.go:61)
[2018-06-18 18:33:59] [0.62ms] INSERT INTO "order_items" ("created_at","updated_at","deleted_at","order_id","item_id","quantity") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'49','145','1')
[1 rows affected or returned ]
(/tmp/main.go.go:62)
[2018-06-18 18:33:59] [0.45ms] INSERT INTO "order_items" ("created_at","updated_at","deleted_at","order_id","item_id","quantity") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'49','146','4')
[1 rows affected or returned ]
(/tmp/main.go.go:69)
[2018-06-18 18:33:59] [0.23ms] SELECT orders.id, orders.status, order_items.order_id, order_items.item_id, order_items.quantity, items.item_name, items.amount FROM "orders" Join order_items on order_items.order_id = orders.id Join items on items.id = order_items.id WHERE (orders.id = '49' and status = 'pending')
[0 rows affected or returned ]
--- ONLY ONE QUERY WAS USED TO FILL THE STRUCT BELOW
2018/06/18 18:33:59 {Model: {ID: 49,
CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
DeletedAt: nil},
Status: "pending",
OrderItems: [{Model: {ID: 0,
CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
DeletedAt: nil},
OrderID: 49,
ItemID: 145,
Item: {Model: {ID: 0,
CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
DeletedAt: nil},
ItemName: "Go Mug",
Amount: 12.489999771118164},
Quantity: 1},
{Model: {ID: 0,
CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
DeletedAt: nil},
OrderID: 49,
ItemID: 146,
Item: {Model: {ID: 0,
CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
DeletedAt: nil},
ItemName: "Go Keychain",
Amount: 6.949999809265137},
Quantity: 4}]}
Related
I want to create a struct movie with the property title and genre data type string, then duration and year data type integer
then create a function with the name addDataFilm to add the data object from the struct to the dataFilm slice, then display the data:
this is my code :
type movie struct {
title, genre string
duration, year int
}
func (m movie) addDataFilm(title string, genre string, duration int, year int, dataFilm *[]string) {
var d = strconv.Itoa(m.duration)
var y = strconv.Itoa(m.year)
*dataFilm = append(*dataFilm, m.title, m.genre, d, y)
}
func main(){
var dataFim = []string{}
var dd = movie{}
dd.addDataFilm("LOTR", "action", 120, 1999, &dataFim)
dd.addDataFilm("Avanger", "action", 120, 2004, &dataFim)
dd.addDataFilm("Spiderman", "action", 120, 2004, &dataFim)
dd.addDataFilm("Juon", "horror", 120, 2004, &dataFim)
fmt.Println(dataFim)
}
all i got is :
any help will be appreciated. thank you in advance
Create a slice of movies. Append each movie to the slice. To print, range over the slice and print each movie.
var movies []*movie
movies = append(movies, &movie{"LOTR", "action", 120, 1999})
movies = append(movies, &movie{"Avanger", "action", 120, 2004})
movies = append(movies, &movie{"Spiderman", "action", 120, 2004})
movies = append(movies, &movie{"Juon", "horror", 120, 2004})
for i, m := range movies {
fmt.Printf("%d. Title: %s\n Genre: %s\n Duration: %d\n Year: %d\n\n", i+1, m.title, m.genre, m.duration, m.year)
}
Run the program on the playground.
The logic can be wrapped up in a type:
// dataFilms stores data for multiple films.
type dataFilms []*movie
func (df *dataFilms) add(title string, genre string, duration int, year int) {
*df = append(*df, &movie{title, genre, duration, year})
}
func (df dataFilms) print() {
for i, m := range df {
fmt.Printf("%d. Title: %s\n Genre: %s\n Duration: %d\n Year: %d\n\n", i+1, m.title, m.genre, m.duration, m.year)
}
}
func main() {
var df dataFilms
df.add("LOTR", "action", 120, 1999)
df.add("Avanger", "action", 120, 2004)
df.add("Spiderman", "action", 120, 2004)
df.add("Juon", "horror", 120, 2004)
df.print()
}
Run it on the playground.
create struct new array of Movies
create global variable Movies to save data
call variable on func main
type Movie struct {
Title string
Genre string
Duration int
Year int
}
type Movies []Movie
var dest Movies
func addDataFilm(title string, genre string, duration int, year int) Movies {
dest = append(dest, Movie{
Title: title,
Genre: genre,
Duration: duration,
Year: year,
})
return dest
}
func TestNumberToAlphabet(t *testing.T) {
addDataFilm("LOTR", "action", 120, 1999)
addDataFilm("Avanger", "action", 120, 2004)
addDataFilm("Spiderman", "action", 120, 2004)
addDataFilm("Juon", "horror", 120, 2004)
fmt.Println(dest)
}
I have 2 lambdas that do the exact same thing, however, they are both written using different langages.
1st lambda - runs on a node.js environment, when I create my arguments to putItem, as follows:
const args = {
id: "my id",
__typename: "a type name",
_version: 1,
_lastChangedAt: now.toISOString(),
createdAt: now.toISOString(),
updatedAt: fields.LastModifiedDate
}
var recParams = {
TableName: dynamoTable,
Key: {
"id": Id
},
Item: args,
ReturnValues: "ALL_OLD"
};
and then I use the docClient to insert the row. Everything works fine, all the properties are populated in my dynamo row.
I have the exact same written in Golang:
item := RecentItem{
Id: "some Id",
_version: 1,
__typename: "a type name",
_lastChangedAt: currentTime.UTC().Format("2006-01-02T15:04:05-0700"),
createdAt: currentTime.UTC().Format("2006-01-02T15:04:05-0700"),
updatedAt: currentTime.UTC().Format("2006-01-02T15:04:05-0700"),
}
av, err := dynamodbattribute.MarshalMap(item)
input := &dynamodb.PutItemInput{
Item: av,
TableName: aws.String(tableName),
}
Everything ALMOST works, the item is inserted, but I am missing all the properties except for the id.
Structure declaration :
type RecentItem struct {
Id string `json:"id"`
_version int `json:"_version"`
_lastChangedAt string `json:"_lastChangedAt"`
createdAt string `json:"createdAt"`
updatedAt string `json:"updatedAt"`
}
Not sure why in Go my dynamoDb row is missing properties. Am I missing something?
Properties other than Id must be exported, i.e, started with an Upper case:
type RecentItem struct {
ID string `dynamodbav:"id"`
Version int `dynamodbav:"_version"`
LastChangedAt string `dynamodbav:"_lastChangedAt"`
CreatedAt string `dynamodbav:"createdAt"`
UpdatedAt string `dynamodbav:"updatedAt"`
}
Suppose we have multiple products with next fields: id, name, type, price, weight.
I want to return products that match some complex filter, for example:
name like '%bla%' and type = 3 - return all products that contains specific substring in name and belongs to specific type
name like '%bla%' and type=4 and weight/price < 10
(name like '%bla%' and type=5) or (name like '%lala%' and type=6 and price < 200)
I don't want to implement separate method for every possible filter.
Repository fetches data from db (I use postgres).
You can do like this:
package main
import(
"fmt"
"regexp"
)
type Items struct{
_id string;
_name string;
_type int;
_price float64;
_weight float64;
}
func (i *Items) filter(bla string) []Items{
items := []Items{
Items{
_id: "i_78676758",
_name: "packet1",
_type: 4,
_price: 44.65,
_weight: 3.6,
},
Items{
_id: "i_546458",
_name: "packet2",
_type: 5,
_price: 234.65,
_weight: 123.6,
},
Items{
_id: "i_5879788",
_name: "packet2",
_type: 5,
_price: 34.65,
_weight: 13.6,
},
Items{
_id: "i_7858758",
_name: "packet3",
_type: 3,
_price: 284.65,
_weight: 23.6,
},
};
var validID = regexp.MustCompile(regexp.QuoteMeta(bla))
new_items := []Items{}
for _, item := range items{
switch{
case ((validID.MatchString(item._name)) && (item._type == 3)):
new_items = items
break;
case ((validID.MatchString(item._name)) && (item._type == 4) && (item._price < 10) && (item._weight < 10)):
new_items = append(new_items, item)
case (((validID.MatchString(item._name)) && item._type == 5) || ((validID.MatchString(item._name)) && (item._type == 6) && (item._price < 200))):
new_items = append(new_items, item)
case ((validID.MatchString(item._name)) && (item._price > 100)):
new_items = append(new_items, item)
default:
}
}
return new_items;
}
func main(){
item := &Items{
_id: "i_7858758",
_name: "packet",
_type: 4,
_price: 234.65,
_weight: 23.6,
}
items := item.filter("et2")
fmt.Println(items)
}
In the above code Items struct holds the data which has filter method which loops over items which is a []Items and filter on the basis of text which is matched by Regexp and switch case to validate other things and finally returns a []Items which holds the filtered data.
I am updating from go-pg from 8.0.5 to 10.3.2 and I am having a problem with the columns ORM
structures:
type Item struct {
ID int `pg:"id,pk"`
Name string `pg:"name"`
Desc string `pg:"description"`
SubItems []SubItem `pg:"rel:has-many"`
}
type SubItem struct {
ID int `pg:"id,pk"`
ItemID int `pg:"item_id"`
Name string `pg:"name"`
Item Item `pg:"rel:has-one"`
}
and this is the section of the unit test that is failing
list := []test.Item{{ID: 1, Name: "first"}, {ID: 2, Name: "second"}, {ID: 3, Name: "third"}}
subItems := []test.SubItem{{ID: 1, ItemID: 1}, {ID: 2, ItemID: 3}}
_, err := dbConn.model(list).Insert()
So(err, ShouldBeNil)
_, err = dbConn.model(subItems).Insert()
So(err, ShouldBeNil)
expected := test.SubItem{
ID: 2,
ItemID: 3,
Item: test.Item{ID: 3, Name: "third"},
}
var actual test.SubItem
err = dbConn.Model(&actual).Column("item").Where("sub_item.id = 2").Select()
So(err, ShouldBeNil)
So(actual, ShouldResemble, expected)
The problem I am running into is, in v8, this selected item with a join. In v10, it's throwing a "column 'item' does not exist" error.
What is the correct way of doing this?
It is changed in v9 check changes here:
Query.Column does not accept relation name any more. Use Query.Relation instead which returns an error if relation does not exist.
Try with Relation then you get all the Item columns.
err = dbConn.Model(&actual).
Column("_").
Relation("Item").
Where("sub_item.id = 2").
Select()
There are more select options with mappings of table relations in docs: Writing queries
I want to relate an entity to itself with GORM
I tried this:
type project struct {
gorm.Model
Name string
ParentID uint
projects []project `gorm:"foreignkey:ParentID,association_foreignkey:ID"`
}
db.Create(&project{Name: "parent", ParentID: 0})
db.Create(&project{Name: "child", ParentID: 1})
db.Create(&project{Name: "child1", ParentID: 1})
var project Project
var projects []Project
db.First(&project)
db.Model(&project).Related(&projects)
but this is the error:
invalid association []
My desired result is:
{Name:"parent",
projects:[
{Name:"child",projects:[]},
{Name:"child1",projects:[]}
]
}
and I want to mention that I am new with golang :)
Change your projects tag to this - gorm:"foreignkey:ParentID" and make the field itself public. To get the parent with children in it do:
p := &project{}
err := db.Where("name = 'parent'").
Preload("Projects").
First(p).Error