How to nest query child's child table data - go

I have a many to many table called users -> tasks, I can query tasks out with this:
var user models.User
DB.Find(&user, "user_addr=?", userAddr)
//log.Infof("find user: ", user)
DB.Model(&user).Related(&tasks, "Tasks")
But, in my tasks, I have a child table with is one to many, called subtasks. How do I query it out as a feild in tasks. Here is my tasks structure:
type Tasks struct {
gorm.Model
TasksID int `json:"tasks_id"`
// TasksCreateTime is the hash value since it is unique
//TasksHash string `json:"tasks_hash"`
// tasks related fields
TasksTitle string `json:"tasks_title"`
TasksCreateTime time.Time `json:"tasks_create_time"`
TasksComment string `json:"tasks_comment"`
SubTasks []SubTask `json:"sub_tasks" gorm:"foreignkey:ID"`
}
I want query the data out and assign it to SubTasks field.
How to do it nested?
If this way works or not?
func FetchSyncTasksForUser(userAddr string, tasks *[]models.Tasks) {
log.Infof("fetch sync tasks for user %s ", userAddr)
var user models.User
DB.Find(&user, "user_addr=?", userAddr)
DB.Preload("Tasks").Preload("Tasks.SubTasks").Find(&user, "user_addr = ?", userAddr)
tasks = &user.Tasks
}

Have you tried the gorm Preload feature? I put together this code snippet which preloads the necessary models. It will print out the formatted models to the command line.
Update: Includes a snippet to find a user by their ID https://gorm.io/docs/query.html
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Tasks []Task
}
type Task struct {
gorm.Model
UserId uint
SubTasks []SubTask
}
type SubTask struct {
gorm.Model
TaskId uint
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
db.AutoMigrate(&User{}, &Task{}, &SubTask{})
// Create
user := User{Name: "C"}
db.Create(&user)
task := Task{UserId: user.ID}
db.Create(&task)
subTaskOne := SubTask{TaskId: task.ID}
subTaskTwo := SubTask{TaskId: task.ID}
db.Create(&subTaskOne)
db.Create(&subTaskTwo)
// Find all users, with tasks and subtasks
users := make([]User, 1)
db.Preload("Tasks").Preload("Tasks.SubTasks").Find(&users)
for _, u := range users {
jsonOutput, err := json.MarshalIndent(u, "", " ")
if err != nil {
panic("")
}
fmt.Println(string(jsonOutput))
}
// Find only one user by user_addr with tasks and subtasks
oneUser := User{}
db.Where("user_addr = ?", user.UserAddr).Preload("Tasks").Preload("Tasks.SubTasks").First(&oneUser)
jsonOutput, err := json.MarshalIndent(oneUser, "", " ")
if err != nil {
panic("")
}
fmt.Println(string(jsonOutput))
// Clean up
db.Delete(&subTaskTwo)
db.Delete(&subTaskOne)
db.Delete(&task)
db.Delete(&user)
}

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!

Get data from two different struct

Have this struct of User and Post and I try to make Name from User to be included within Post Struct when a user create a new post.
type User struct {
ID int
Name string
Created time.Time
}
type Post struct {
ID int
PostTitle string
PostDesc string
Created time.Time
}
How can I create something connected between this two struct such as Author of the Post ?
The goal is try to get the name of the post author which from User struct with the code below:
post, err := app.Models.Posts.GetPost(id)
GetPost() just run SELECT query and scan row
This approach is without any ORM.
It's a simple query that can return multiple rows. You've to scan the whole resultset and map each column on the struct's fields.
Keep in mind to always check for errors.
Below you can find the solution:
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/lib/pq"
)
type Post struct {
ID int
PostTitle string
PostDesc string
Created time.Time
UserID int
User User
}
type User struct {
ID int
Name string
Created time.Time
}
func main() {
conn, err := sql.Open("postgres", "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable")
if err != nil {
panic(err)
}
defer conn.Close()
// get MAX id
var id int
conn.QueryRow(`SELECT MAX(id) FROM posts`).Scan(&id)
// insert
sqlInsertStmt := `INSERT INTO posts (id, post_title, post_desc, created, user_id) VALUES ($1,$2,$3,$4,$5)`
if _, err = conn.Exec(sqlInsertStmt, id+1, "TDD", "Introduction to Test-Driven-Development", time.Now(), 1); err != nil {
panic(err)
}
// read
rows, err := conn.Query(`SELECT posts.id, post_title, post_desc, posts.created, users.id, users.name, users.created FROM posts INNER JOIN users ON posts.user_id=users.id`)
if err != nil {
panic(err)
}
var posts []Post
for rows.Next() {
var post Post
if err = rows.Scan(&post.ID, &post.PostTitle, &post.PostDesc, &post.Created, &post.User.ID, &post.User.Name, &post.User.Created); err != nil {
panic(err)
}
posts = append(posts, post)
}
if err = rows.Err(); err != nil {
panic(err)
}
for _, v := range posts {
fmt.Printf("author name: %q\n", v.User.Name)
}
}
Let me know if this helps.
Edit
I've also included an example of INSERT in Postgres. To achieve it, we've to use the db.Exec() function, and provide the parameters.
Pay attention to how you construct the query as you can get a SQL-Injection vulnerability.
Lastly, in a real-world scenario, you shouldn't lookup for the MAX id in the posts table but should be automatically generated.
Give it a try to this solution, maybe it resolves your issue.
First, I defined the structs in this way:
// "Post" belongs to "User", "UserID" is the foreign key
type Post struct {
gorm.Model
ID int
PostTitle string
PostDesc string
Created time.Time
UserID int
User User
}
type User struct {
ID int
Name string
Created time.Time
}
In this way, you can say that Post belongs to User and access the User's information within the Post struct.
To query the records, you've to use Preload("User") to be sure to eager load the User records from the separate table.
Keep attention to the name you pass in as the argument in Preload, as it can be tricky.
Lastly, you'll be able to access data in the embedded struct (with the dot notation).
Below, you can find a complete working example (implemented with the use of Docker):
package main
import (
"fmt"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// "Post" belongs to "User", "UserID" is the foreign key
type Post struct {
gorm.Model
ID int
PostTitle string
PostDesc string
Created time.Time
UserID int
User User
}
type User struct {
ID int
Name string
Created time.Time
}
func main() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Post{})
newPost := &Post{ID: 1, PostTitle: "Golang", PostDesc: "Introduction to Golang", Created: time.Now(), UserID: 1, User: User{ID: 1, Name: "John Doe", Created: time.Now()}}
db.Create(newPost)
var post Post
db.Preload("User").Find(&post, 1)
fmt.Printf("author name: %q\n", post.User.Name)
}
Let me know if I answered your question!

How to get and store the document ID using a struct

I've defined a data structure like so:
type Person struct {
Name string `firestore:"name,omitempty"`
}
When I query all the documents in a collection I'd like to be able to attach the ID to the documents for later reference, but not necessarily have ID as an attribute stored in Firestore (unless its the only way). In javascript or python this is straightforward as the data structures are dynamic and I can just query the ID post get() and add it as a dynamic key/value. myObj.id = doc.id
How would I do this with Go?
package main
import (
"fmt"
"cloud.google.com/go/firestore"
"context"
"google.golang.org/api/iterator"
"log"
)
type Person struct {
Name string `firestore:"name,omitempty"`
}
func main() {
ctx := context.Background()
c, err := firestore.NewClient(ctx, "my-project")
if err != nil {
log.Fatalf("error: %v", err)
}
var people []Person
iter := c.Collection("people").Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("error: %v", err)
}
var p Person
err = doc.DataTo(p)
if err != nil {
log.Fatalf("error: %v", err)
}
// id := doc.Ref.ID
people = append(people, p)
}
fmt.Println(people)
}
output, no ID
>> [{John Smith}]
I believe that the firestore struct tags works the same way as the tags in the encoding/json package. So a tag with a value of "-" would mean ignore the field.
So
type Person struct {
ID int `firestore:"-"`
Name string `firestore:"name,omitempty"`
}
should do the trick.
You can set ID yourself, but the firestore pkg will ignore it when reading/writing data.
If you want to store the firestore document ID on the Person type, your struct must have a declared field for it.
Golang firestore docs don't mention this explicitly, but since a firestore doc ID is not part of the document fields, the func (*DocumentSnapshot) DataTo does not populate the ID. Instead, you may get the document ID from the DocumentRef type and add it to Person yourself.
The doc also states that:
Note that this client supports struct tags beginning with "firestore:" that work like the tags of the encoding/json package, letting you rename fields, ignore them, or omit their values when empty
Therefore, if you want to omit the ID when marshaling back for firestore, your could use the tag firestore:"-"
The Person would look like this:
type Person struct {
ID string `firestore:"-"`
Name string `firestore:"name,omitempty"`
}
inside the loop:
var p Person
err := docSnap.DataTo(&p)
if err != nil {
// handle it
}
p.ID = doc.Ref.ID

Go gorm check a model is associated with another model

I have a race model
type Race struct {
gorm.Model
Title string
Date string
Token string
Heats []Heat `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Runners []Runner `gorm:"many2many:race_runners;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
I want to add runners into heats but before doing so I want to assert that the runner actually is in the race
var race models.Race
if err := models.DB.Preload("Runners").Find(&race, "runners.id IN ?", []uint{runner.ID}).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Runner not in race!"})
return
}
I'm getting the following error
near "?": syntax error
Because I'm having a many to many relationship between Race and Runners I doubt that I can just do runners.id in the condition I pass to my Find method. But I'm not sure how to actually do what I want to achieve. Which is ensure the runner is already in the race before adding it to a heat. Any suggestions?
The where query wants to be in the Preload part. Then you can check the len(fetchedRace.Runners) < 1, and if that is true, the runner is not in that race.
fetchedRace := Race{}
db.Preload("Runners", "runners.id = ?", runnerOne.ID).Find(&fetchedRace, race.ID)
Full working example
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Race struct {
gorm.Model
Title string
Date string
Token string
Runners []Runner `gorm:"many2many:race_runners;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type Runner struct {
gorm.Model
Name string
}
func main() {
db, err := gorm.Open(sqlite.Open("many2many.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
_ = db.AutoMigrate(&Race{}, &Runner{})
raceOne := Race{
Title: "Race One",
}
db.Create(&raceOne)
runnerOne := Runner{
Name: "Runner One",
}
runnerTwo := Runner{
Name: "Runner Two",
}
db.Create(&runnerOne)
db.Create(&runnerTwo)
// Associate runners with race
err = db.Model(&raceOne).Association("Runners").Append([]Runner{runnerOne, runnerTwo})
// Fetch from DB
fetchedRace := Race{}
db.Debug().Preload("Runners", "runners.id = ?", runnerOne.ID).Find(&fetchedRace, raceOne.ID)
if len(fetchedRace.Runners) < 1 {
fmt.Println("error, runner not in race")
}
db.Delete(&raceOne)
db.Delete(&runnerOne)
db.Delete(&runnerTwo)
}
Using a custom SQL statement
This can be done in one SQL statement and gorm isn't very optimal with it's preloading, so if you are planning on using this often I would probably do something like this:
func isRunnerInRace(db *gorm.DB, runnerId uint, raceId uint) bool {
count := 0
db.Raw("SELECT COUNT(runner_id) FROM race_runners WHERE race_id = ? AND runner_id = ?", raceId,
runnerId).Scan(&count)
return count > 0
}

Preloading 'belongs to' associations both ways

I'd like to use GORM's 'belongs to' association in a way similar to Django's one-to-one relationships. Consider the following example in which each User is associated with one Profile:
package main
import (
"fmt"
"os"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/sirupsen/logrus"
)
type User struct {
gorm.Model
Name string
}
func (user User) String() string {
return fmt.Sprintf("User(Name=%s)", user.Name)
}
type Profile struct {
gorm.Model
UserID uint
User User
Name string
}
func (profile Profile) String() string {
return fmt.Sprintf("Profile(Name=%s, User=%d)", profile.Name, profile.UserID)
}
func (user *User) AfterCreate(scope *gorm.Scope) error {
profile := Profile{
UserID: user.ID,
Name: user.Name,
}
return scope.DB().Create(&profile).Error
}
const dbName = "examplegorm.db"
func main() {
db, err := gorm.Open("sqlite3", dbName)
if err != nil {
logrus.Fatalf("open db: %v", err)
}
defer func() {
db.Close()
os.Remove(dbName)
}()
db.LogMode(true)
db.AutoMigrate(&User{})
db.AutoMigrate(&Profile{})
user := User{Name: "jinzhu"}
if err := db.Create(&user).Error; err != nil {
logrus.Fatalf("create user: %v", err)
}
var profile Profile
if err := db.Where(Profile{UserID: user.ID}).Preload("User").First(&profile).Error; err != nil {
logrus.Fatalf("get profile: %v", err)
}
logrus.Infof("profile: %v", profile)
logrus.Infof("user: %v", profile.User)
}
In this example, I query for a Profile and preload its User. I would actually like to do this the other way, however: query a User and preload its Profile.
As I understand it, in Django you would be able to access both the profile.user and the user.profile, but if I try to add Profile and ProfileID fields to the User model,
type User struct {
gorm.Model
Name string
Profile
ProfileID uint
}
I get an 'invalid recursive type' error:
# command-line-arguments
./gorm_belongs_to.go:23:6: invalid recursive type Profile
Is there any way to get a user.Profile in this GORM example?
I think that the problem in your case is using the name Profile which is the same as the type.
If your User struct will look something like this it should work:
type User struct {
gorm.Model
Name string
UserProfile Profile
UserProfileID uint
}

Resources