How to query association after successful db.Save() - mysql

I am working with Gorm and Graphql. I hadn't run into problems querying data until I tried to connect two existing items using their foreignkey relationships. Here are my two models:
type Report struct {
db.Base
OwnerID string
Patient patients.Patient `gorm:"association_name:Patient;"`
PatientID string
}
type Patient struct {
db.Base
ReportID string
}
I have a function to save the relationship to the database:
func (s *Store) AddPatientToReport(ctx context.Context, id string, patient *patients.Patient) (*Report, error) {
// check against user using context
report, err := s.Report(ctx, id)
if err != nil {
log.Error("Could not find report.")
return nil, err
}
report.PatientID = patient.ID
if err := s.db.Save(&report).Association("Patient").Append(patient).Error; err != nil {
log.WithError(err).Error("add patient failed")
return nil, err
}
return report, nil
}
After the above function I can query the Report and see the patient_id. I can also query the Patient and see the report_id. But the following query to get the whole Patient from the Report just returns empty.
query {
report(id: "report_id") {
id
patientID // this will return the correct patient_id
patient {
id // this field always comes back as an empty string
}
}
}
Here is how the DB is setup:
// NewConn creates a new database connection
func NewConn(cc ConnConf) (*Conn, error) {
db, err := gorm.Open("mysql", cc.getCtxStr())
if err != nil {
return nil, err
}
// Models are loaded from each package. Patients is created before Reports.
if err := db.AutoMigrate(Models...).Error; err != nil {
log.Fatal(err)
}
db.LogMode(cc.Logger)
return &Conn{db}, err
}
I can't figure out how to get the whole patient back. Any suggestions?

Ok, so as it turns out I just needed to ask and then I'd figure it out on my own a few minutes later.
I'd read about Preload() in the Gorm docs, but didn't know where to implement it. I first tried when the DB fired up thinking it would load the associations. But I really needed to use Preload() as I run the query.
result := &Report{}
if err = s.db.Preload("Patient").Where(&query).First(&result).Error; err != nil {
log.WithField("id", id).WithError(err).
Error("could not find report")
return nil, err
}
Now, the graphql query:
query {
report(id: "some_id") {
id
patient {
id // now it returns the id
birthyear // now it returns other fields, too
...
}
}
}

Related

How can I access JSON column in mysql db from golang

I have a table in mysql with 3 columns Profile string, userInformation JSON, badge string.
Profile
userInformation
badge
https://ps.w.org/metronet-profile-picture/assets/icon-256x256.png?rev=2464419
{"name": "Suzan Collins", "points": 10000, "countryName": "Poland"}
assets/batcherPage/gold.png
This is my struct:
type BatchersData struct {
ProfileURL string `json:"profileUrl"`
UserInformation UserInformation `json:"userInformation"`
Badge string `json:"badge"`
}
type UserInformation struct {
Points int64 `json:"points"`
Name string `json:"name"`
CountryName string `json:"countryName"`
}
What I want to do is make a select query on this table ie GET and retrieve every information..
using this code, I have accessed Profile and Badge :
func getBatchers(c *gin.Context) {
var batchers []BatchersData
rows, err := db.Query("SELECT profileUrl, badge FROM Batchers_page_db")
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var batcher BatchersData
if err := rows.Scan(&batcher.ProfileURL, &batcher.Badge); err != nil {
return
}
batchers = append(batchers, batcher)
}
if err := rows.Err(); err != nil {
return
}
c.IndentedJSON(http.StatusOK, batchers)
}
But I want to access JSON column ie UserInformation as well. I know that the query will be
rows, err := db.Query("SELECT * FROM Batchers_page_db")
But i'll have to make a change in this statement
if err := rows.Scan(&batcher.ProfileURL, &batcher.Badge);
I have tried doing this : but nothing works
rows.Scan(&batcher.ProfileURL,&batcher.UserInformation, &batcher.Badge);
You need to implement Scan interface doc to map the data to JSON. Here try this:
func (u * UserInformation) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(b, &u)
}
SELECT JSON_EXTRACT(userInformation,'$.name') as profileName,
JSON_EXTRACT(userInformation,'$.points') as userPoints from
Batchers_page_db
Not tested, but something like this will work but make sure your database field must be JSON type.

More concise function to fetch SQL result set in GO Golang

I want to retrieve an array of app IDs from a MySQL database. I used http://go-database-sql.org's example code:
func QueryAppList() *[]int64 {
var (
appList []int64
appid int64
)
qry := "SELECT a.appid FROM app a WHERE a.app_status IN (1, 2);"
// cfg.GetDb() supplies the database connection already established
rows, err := cfg.GetDb().Query(qry)
if err != nil {
logg.Error(err)
return &appList
}
defer func(rows *sql.Rows) {
// simple defer does not catch every error: https://www.joeshaw.org/dont-defer-close-on-writable-files/
err := rows.Close()
if err != nil {
logg.Error(err)
}
}(rows)
for rows.Next() {
err := rows.Scan(&appid)
if err != nil {
logg.Error(err)
return &appList
}
appidList = append(appList, appid)
}
err = rows.Err()
if err != nil {
logg.Error(err)
return &appList
}
return &appidList
}
My programm will be littered with queries like this. All the ways of getting the result list and how it to prevent failure make this small query hard to read what is actually going on.
Is there a way to make queries more concise?
These are my thoughts to make the code less verbose:
Use functions to handle the errors reducing the error handling to one line.
If it's one column array I want, I could pass the query and column name as parameters and reuse the query function. I rather just rewrite a query function than to deal with complicated abstractions.
Are there packages I missed that help reduce the clutter?
Using an ORM like gorm is NOT an option.
I just started Go programming so I am lacking experience with the language.
Below is the same query in Node.js with the same result. It has 9 lines compared to Go's 34 i.e. 65% more concise in terms of length. That's where I ideally would like to get to.
import {query} from "../db/pool"; // connection pool query from https://github.com/sidorares/node-mysql2
export const queryAppList = async () => {
try {
const qry = "SELECT a.appid FROM app a WHERE a.app_status IN (1, 2);";
const [appList] = await query(qry);
return appList;
} catch (err) {
console.error(err)
return [];
}
};
You can make a Query struct which has reusable methods for do such things.
Something like this:
type Query struct{
conn *sql.DB
rows *sql.Rows
...
}
func NewQuery(conn *sql.DB) *Query {
return &Query{
conn: conn,
rows: nil,
}
}
func (q Query) OpenSQL(sql string) error {
q.rows, err = q.conn.Query(sql)
if err != nil {
log.Error("SQL error during query ("+sql+"). "+err.Error())
return err
}
return nil
}
func (q Query)Close() (error) {
err := q.rows.Close()
if err != nil {
log.Error("Error closing rows. "+err.Error())
return err
}
return nil
}
//You can use generic functions to make the code even smaller
func FetchToSlice[T any](q Query) ([]T, error) {
result := make([]T, 0)
var value T
for q.rows.Next() {
err := q.rows.Scan(&value)
if err != nil {
log.Error("Error during fetching. "+err.Error())
return nil, err
}
result = append(result, value)
}
return result, nil
}
With this you code will look something like this:
qry := NewQuery(cfg.GetDB())
err := qry.OpenSQL("SELECT a.appid FROM app a WHERE a.app_status IN (1, 2);")
if err != nil {
return err
}
defer qry.Close()
appidList, err := FetchToSlice[int](qry)
if err != nil {
return err
}
You can later add more methods to your Query to handle more complex cases, even you can use a sync.Pool to cache your query structs and so on.

how to parse multiple query params and pass them to a sql command

i am playing around rest API's in go and when i do a get call with this
http://localhost:8000/photos?albumId=1&id=1
i want to return only those values from db which corresponds to alubmId=1 and id =1 , or any other key in the query for that matter without storing as variables and then passing it to query, and when i dont give any query params i want it to return all the posts in db
func getPhotos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var photos []Photo
db, err = sql.Open("mysql", "root:$pwd#tcp(127.0.0.1:3306)/typicode")
if err != nil {
panic(err.Error())
}
defer db.Close()
for k, v := range r.URL.Query() {
fmt.Printf("%s: %s\n", k, v)
}
result, err := db.Query("SELECT id, albumId, Title,thumbnailUrl,url from photo")
if err != nil {
panic(err.Error())
}
defer result.Close()
for result.Next() {
var photo Photo
err := result.Scan(&photo.Id, &photo.AlbumId, &photo.Title, &photo.Url, &photo.Thumbnailurl)
if err != nil {
panic(err.Error())
}
photos = append(photos, photo)
}
json.NewEncoder(w).Encode(photos)
}
First you need a set of valid table columns that can be used in the query, this is required to avoid unnecessary errors from columns with typos and sql injections from malicious input.
var photocolumns = map[string]struct{}{
"id": {},
"albumId": {},
// all the other columns you accept as input
}
Now depending on the database you may, or you may not, need to parse the query values and convert them to the correct type for the corresponding column. You can utilize the column map to associate each column with the correct converter type/func.
// a wrapper around strconv.Atoi that has the signature of the map-value type below
func atoi(s string) (interface{}, error) {
return strconv.Atoi(s)
}
var photocolumns = map[string]func(string) (interface{}, error) {
"id": atoi,
"albumId": atoi,
// all the other columns you accept as input
}
Then, all you need is a single loop and in it you do all the work you need. You get the correct column name, convert the value to the correct type, aggregate that converted value into a slice so that it can be passed to the db, and also construct the WHERE clause so that it can be concatenated to the sql query string.
where := ""
params := []interface{}{}
for k, v := range r.URL.Query() {
if convert, ok := photocolumns[k]; ok {
param, err := convert(v[0])
if err != nil {
fmt.Println(err)
return
}
params = append(params, param)
where += k + " = ? AND "
}
}
if len(where) > 0 {
// prefix the string with WHERE and remove the last " AND "
where = " WHERE " + where[:len(where)-len(" AND ")]
}
rows, err := db.Query("SELECT id, albumId, Title,thumbnailUrl,url from photo" + where, params...)
// ...

How can I ensure that all of my write transaction functions get resolved in order? Also, why is the else function not executing?

I'm trying to create a very simple Bolt database called "ledger.db" that includes one Bucket, called "Users", which contains Usernames as a Key and Balances as the value that allows users to transfer their balance to one another. I am using Bolter to view the database in the command line
There are two problems, both contained in this transfer function issue resides in the transfer function.
The First: Inside the transfer function is an if/else. If the condition is true, it executes as it should. If it's false, nothing happens. There's no syntax errors and the program runs as though nothing is wrong, it just doesn't execute the else statement.
The Second: Even if the condition is true, when it executes, it doesn't update BOTH the respective balance values in the database. It updates the balance of the receiver, but it doesn't do the same for the sender. The mathematical operations are completed and the values are marshaled into a JSON-compatible format.
The problem is that the sender balance is not updated in the database.
Everything from the second "Success!" fmt.Println() function onward is not processed
I've tried changing the "db.Update()" to "db.Batch()". I've tried changing the order of the Put() functions. I've tried messing with goroutines and defer, but I have no clue how to use those, as I am rather new to golang.
func (from *User) transfer(to User, amount int) error{
var fbalance int = 0
var tbalance int = 0
db, err := bolt.Open("ledger.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
return db.Update(func(tx *bolt.Tx) error {
uBuck := tx.Bucket([]byte("Users"))
json.Unmarshal(uBuck.Get([]byte(from.username)), &fbalance)
json.Unmarshal(uBuck.Get([]byte(to.username)), &tbalance)
if (amount <= fbalance) {
fbalance = fbalance - amount
encoded, err := json.Marshal(fbalance)
if err != nil {
return err
}
tbalance = tbalance + amount
encoded2, err := json.Marshal(tbalance)
if err != nil {
return err
}
fmt.Println("Success!")
c := uBuck
err = c.Put([]byte(to.username), encoded2)
return err
fmt.Println("Success!")
err = c.Put([]byte(from.username), encoded)
return err
fmt.Println("Success!")
} else {
return fmt.Errorf("Not enough in balance!", amount)
}
return nil
})
return nil
}
func main() {
/*
db, err := bolt.Open("ledger.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
*/
var b User = User{"Big", "jig", 50000, 0}
var t User = User{"Trig", "pig", 40000, 0}
// These two functions add each User to the database, they aren't
// the problem
b.createUser()
t.createUser()
/*
db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("Users"))
get := c.Get([]byte(b.username))
fmt.Printf("The return value %v",get)
return nil
})
*/
t.transfer(b, 40000)
}
I expect the database to show Big:90000 Trig:0 from the beginning values of Big:50000 Trig:40000
Instead, the program outputs Big:90000 Trig:40000
You return unconditionally:
c := uBuck
err = c.Put([]byte(to.username), encoded2)
return err
fmt.Println("Success!")
err = c.Put([]byte(from.username), encoded)
return err
fmt.Println("Success!")
You are not returning and checking errors.
json.Unmarshal(uBuck.Get([]byte(from.username)), &fbalance)
json.Unmarshal(uBuck.Get([]byte(to.username)), &tbalance)
t.transfer(b, 40000)
And so on.
Debug your code statement by statement.

Golang - retrieve multiple results from MySQL, then display them as JSON

Recently, I'm learning about Go (Golang). I'm trying to make a simple web service using Martini and jwt-go. I didn't find any difficulty in retrieving a single row data and put in JSON as the response. But, when dealing with multiple-rows, it's a whole different story. Basically, I refer to the accepted answer here.
Here is the piece of my code:
m.Get("/users", func(params martini.Params, r render.Render) {
db, err := sql.Open("mysql", "root:#/sirat_v2")
if err != nil {
panic(err.Error())
}
defer db.Close()
rows, err := db.Query("SELECT user_id, nama FROM `users` WHERE password = ?", "asdfasdf")
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
panic(err.Error())
}
partages := make([]*Partage, 0, 10)
var user_id int
var nama string
for rows.Next() {
err = rows.Scan(&user_id, &nama)
if err != nil { /* error handling */
}
partages = append(partages, &Partage{user_id, nama})
}
})
When trying to build, there's an error said that Partage is undefined.
The error showing up because you use struct Partage to create an object, but you haven't declared it.
type Partage struct {
user_id string
nama string
}
But how do I display the result as JSON response? I've tried r.JSON(200, partages) but the results aren't displayed
In martini you can use r.JSON() to print rows as JSON
m.Get("/users", func(params martini.Params, r render.Render) {
// ...
r.JSON(200, map[string]interface{}{
data: rows
})
})