Best practices to perform a JOIN with SQLX, Squirrel - postgresql

I have this model.
// Incident is a security incident.
type Incident struct {
ID string `json:"id" bson:"_id"`
Title string `json:"title" bson:"title"`
Description string `json:"description" bson:"description"`
Issue *Issue `json:"issue" bson:"issue"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
ModifiedAt time.Time `json:"modified_at" bson:"modified_at"`
}
// Issue is a security issue.
type Issue struct {
ID string `json:"id" bson:"_id"`
Title string `json:"title" bson:"title"`
IncidentID string `json:"incident_id"`
Description string `json:"description" bson:"description"`
}
Each Incident has one Issue.
When I insert an Incident into Postgres, I only add the issue_id.
type incident struct {
ID string `db:"id"`
CustomerID string `db:"customer_id"`
InternalID string `db:"internal_id"`
Title string `db:"title"`
IssueID string `db:"issue_id"`
Description string `db:"description"`
CreatedAt time.Time `db:"created_at"`
ModifiedAt time.Time `db:"modified_at"`
}
func toIncident(model *models.Incident) (*incident, error) {
// Create the SQL
incident := &sqlIncident{
ID: model.ID,
CustomerID: model.CustomerID,
InternalID: model.InternalID,
Title: model.Title,
Description: model.Description,
IssueID: "",
CreatedAt: model.CreatedAt,
ModifiedAt: model.ModifiedAt,
}
// Add the issue ID
if model.Issue != nil {
incident.IssueID = model.Issue.ID
}
return incident, nil
}
I would like to be able to do the reverse operation with a JOIN on the Issue when I get() or list() incidents.
I am using "github.com/Masterminds/squirrel" and "github.com/jmoiron/sqlx".
This code below would be the code to get an Incident.
// Prepare query
query := squirrel.Select(*).
From(r.GetTableName()).
PlaceholderFormat(squirrel.Dollar).
Join("????????")
// Build the SQL query
q, args, err := query.ToSql()
if err != nil {
return fmt.Errorf("postgresql: unable to build query: %w", err)
}
// Get the session repo
session := r.GetSession().(*sqlx.DB)
// Prepare the statement
stmt, err := session.PreparexContext(ctx, q)
if err != nil {
return fmt.Errorf("postgresql: unable to prepapre query: %w", err)
}
// Do the query
err = stmt.SelectContext(ctx, result, args...)
if err == sql.ErrNoRows {
return repositories.ErrNoResult
} else if err != nil {
return fmt.Errorf("postgresql: unable to execute query: %w", err)
}
How can I perform this correctly please ?
I am feeling that I am doing this wrong with the issue_id field that seems to be useless, and that I should add the definition of the Issue in my incident SQL structure, something like this below.
type incident struct {
ID string `db:"id"`
CustomerID string `db:"customer_id"`
InternalID string `db:"internal_id"`
Title string `db:"title"`
Issue issue `db:"issue"`
Description string `db:"description"`
CreatedAt time.Time `db:"created_at"`
ModifiedAt time.Time `db:"modified_at"`
}
But I can't see the next steps.

Related

How to insert string as ISODate to mongo db, using golang?

I have the following transaction struct
type Transaction struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Category primitive.ObjectID `bson:"category,omitempty"`
Amount string `json:"amount" binding:"required"`
Owner primitive.ObjectID `bson:"owner,omitempty"`
Date primitive.DateTime `bson:"date" json:"date"`
//Date time.Time `bson:"date" json:"date"`
}
var transaction models.Transaction
if err := c.ShouldBindJSON(&transaction); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
createdTransaction, createErr := handler.collection.InsertOne(handler.ctx, transaction)
I am trying to pass the date, in the following format Mon Jan 30 2023 17:27:16 GMT+0200 (Eastern European Standard Time), and I get the Bad Request error.
How can I insert the following date format as ISODate into mongodb?
type Transaction struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Category primitive.ObjectID `bson:"category,omitempty" json:"category"`
Amount string `json:"amount" binding:"required"`
Owner primitive.ObjectID `bson:"owner,omitempty" json:"owner"`
InvDt primitive.DateTime `bson:"invdt,omitempty" json:"invdt,omitempty"`
Date string `json:"date" binding:"required"`
Cat []map[string]interface{} `json:"cat" bson:"cat"`
}
const shortForm = "2006-01-02"
dt, _ := time.Parse(shortForm, transaction.Date)
transaction.InvDt = primitive.NewDateTimeFromTime(dt)
As was mentioned before, i need to use parse time as string
Change this line:
createdTransaction, createErr := handler.collection.InsertOne(handler.ctx, transaction)
To include &transaction like this as the final parameter InsertOne takes a reference to the data to be stored, not a value:
createdTransaction, createErr := handler.collection.InsertOne(handler.ctx, &transaction)

fetching data and upsert into database

I want to get data from api and then doing query into specific tables using upsert, for example :
Repository
// get data from api
response, resErr := http.Get("look at json that want to fetch")
if resErr != nil {
fmt.Print(resErr.Error())
return resErr
}
// Read response from the API
defer response.Body.Close()
responseOdoo, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
//unmarshaling data to struct
var responseObject []models.OdooResponse
json.Unmarshal(responseOdoo, &responseObject)
here I want to perform an upsert to an existing table, but I don't know how to combine the data that has been retrieved and then fill it into the query
// get data from table and then upsert into column
qapi := `
insert into services_new
(code, service_id, service_name, service_category_id, service_category, service_type_id, service_type , price, request_type, status, uom_code, created_at , created_by, created_by_name, updated_at, updated_by, updated_by_name)
values
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,NOW(),$12,$13,NOW(),$12,$13)
on conflict (service_id, service_category_id, service_type_id)
do update set service_name =excluded.service_name, service_category =excluded.service_category, service_type =excluded.service_type, price =excluded.price, request_type =excluded.request_type, status =excluded.status, uom_code =exluded.uom_code, created_at =excluded.created_at, created_by =excluded.created_by, created_by_name =excluded.created_by_name;
`
_, sqlErr := tx.ExecContext(ctx, qapi, request.Code, request.ServiceID, request.ServiceName, request.ServiceCategoryID, request.ServiceCategory, request.ServiceTypeID, request.ServiceType, request.Price, request.RequestType, request.Status, request.UomCode, helper.Fullname, helper.Email)
// checking query upsert services_new
if sqlErr != nil {
tx.Rollback()
log.Println("sql Error on Repository Upsert on ODOO Services", sqlErr)
return sqlErr
}
txErr = tx.Commit()
if txErr != nil {
return txErr
}
return nil
until here, is the flow correct?
JSON That want to fetch
{
"service_id": 1129,
"service_name": "Adobe Illustrator",
"service_category_id": 28,
"service_category_name": "License Software",
"service_type_id": 25,
"service_type_name": "Software",
"create_date": "2020-03-09 03:47:44"
},
struct models
type UpsertFromOdooServices struct {
Code string `json:"code"`
ServiceID uint `json:"service_id"`
ServiceName string `json:"service_name"`
ServiceCategoryID uint `json:"service_category_id"`
ServiceCategory uint `json:"service_category"`
ServiceTypeID uint `json:"service_type_id"`
ServiceType string `json:"service_type"`
Price float64 `json:"price"`
RequestType string `json:"request_type"`
Status string `json:"status"`
UomCode string `json:"uom_code"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by"`
CreatedByName string `json:"created_by_name"`
UpdatedAt time.Time `json:"updated_at"`
UpdatedBy string `json:"updated_by"`
UpdatedByName string `json:"updated_by_name"`
}
type OdooResponse struct {
ServiceID uint `json:"service_id"`
ServiceName string `json:"service_name"`
ServiceCategoryID uint `json:"service_category_id"`
ServiceCatrgoryName string `json:"service_category_name"`
ServiceTypeID uint `json:"service_type_id"`
ServiceTypeName string `json:"service_type_name"`
Response []UpsertFromOdooServices
}

how to make request body in gorm for Association belongs to and has one

I have People and Data , where People has one Data and Data belongs to People
how to make a request body JSON for that Association in go gin? I am using gorm for this case,
the documentation of gorm is not clear for me for this case,
i was supposed like
func CreateTodo(db *gorm.DB) func(c *gin.Context) {
var person Person
var data Data
c.bindJSON(&Person)
c.bindJSON(&Data)
db.create(&Person)
db.create(&Data)
c.JSON(200, gin.H{ result : []interface{person, data})
}
type (
Data struct {
ID uint `gorm:"auto_increment"`
PersonID uint
Person *Person `gorm:"foreignkey:PersonID;association_foreignkey:id"`
Address string
Languange string
}
Person struct {
gorm.Model
Email string `gorm:"type:varchar(100);unique_index;not null"`
Password string `gorm:"type:varchar(100);not null"`
Role string `gorm:"size:30;not null"`
DataID uint
Data *Data `gorm:""foreignkey:DataID;association_foreignkey:id"`
}
)
I am sure it will not make the person_id and data_id for FK
what I ask, how I can make the request body for that Association until those request created with FK itself ? should I create then update again for person_id and data_id after it created ??
Gorm will do almost everything for an association link. It seems that "DataID" in your Person struct is useless. See the code below for an example:
package main
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type (
Data struct {
ID uint `gorm:"auto_increment"`
PersonID uint
Person *Person `gorm:"foreignkey:PersonID;association_foreignkey:id"`
Address string
Languange string
}
Person struct {
gorm.Model
Email string `gorm:"type:varchar(100);unique_index;not null"`
Password string `gorm:"type:varchar(100);not null"`
Role string `gorm:"size:30;not null"`
Data *Data `gorm:""foreignkey:PersonID;association_foreignkey:id"`
}
)
func main() {
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
panic("failed to connect database")
}
db.LogMode(true)
defer db.Close()
// Migrate the schema
db.AutoMigrate(&Person{}, &Data{})
data := &Data{
Address: "Shanghai,China",
Languange: "Chinese",
}
person := &Person{
Email: "zhjw43#163.com",
Data: data,
}
db.Save(person)
db.DropTable("data", "people")
}

How to access array values present in mongodb?

I want to access array values (access SpecCode) present in mongodb database from Go.
type MTopic struct {
SpecCodes []struct {
SpecCode string `json:speccode`
}
TopicCode string `json:topiccode`
TopicDesc string `json:topicdesc`
TopicBigDesc string `json:topicbigdesc`
TopicSource string `json:topicsource`
TopicSources []struct {
Topic string `json:topic`
}
CreatedBy string `json:createdby`
CreatedOn string `json:createdon`
UpdatedBy string `json:updatedby`
UpdatedOn string `json:updatedon`
}
using the following code:
func (m *TopicMaster) GetTopic(userdetails string) (list []MTopic, err error) {
collection := dbConnect7.Use("masterdata", "topic_master")
err = collection.Find(bson.M{"speccodes": userdetails}).All(&list)
return list, err
}
I have to get all values which have speccodes of userdetails in the collection topic_master. It's gin framework. This code is from models.
Just try like this
type MTopic struct {
SpecCodes []struct {
SpecCode string `json:"speccode"`
} `json:"speccodes"`
TopicCode string `json:"topiccode"`
TopicDesc string `json:"topicdesc"`
TopicBigDesc string `json:"topicbigdesc"`
TopicSource string `json:"topicsource"`
TopicSources []struct {
Topic string `json:"topic"`
}
CreatedBy string `json:"createdby"`
CreatedOn string `json:"createdon"`
UpdatedBy string `json:"updatedby"`
UpdatedOn string `json:"updatedon"`
}
and your function should be like this
func (m *TopicMaster) GetTopic(userdetails string) (list []MTopic, err error) {
collection := dbConnect7.Use("masterdata", "topic_master")
findQ := bson.M{"speccodes.speccode": userdetails}
list := make([]MTopic, 0)
if err = collection.Find(findQ).All(&list); err != nil {
err=errors.Wrapf(err, "failed to fetch topic info for user detail %s", userdetails)
return nil, err
}
return list, err
}

I'm trying to find a record by email

I have the following code.
Handler
func (authHandler *AuthHandler) Login(c *gin.Context) {
var user models.User
c.Bind(&user)
if &user == nil {
c.BindJSON(&user)
}
userObject, err := authHandler.userRepo.FindBy(
models.User{
Email: user.Email,
},
)
if err != nil {
c.JSON(401, gin.H{"_message": "User not found."})
return
}
passErr := bcrypt.CompareHashAndPassword([]byte(userObject.Password), []byte(user.Password))
if passErr != nil {
c.JSON(401, gin.H{"_message": "Password incorrect."})
return
}
token, err := services.CreateToken(userObject)
if err != nil {
c.JSON(401, gin.H{"_message": "Password incorrect."})
return
}
c.JSON(200, gin.H{"token": token, "user": gin.H{
"id": &userObject.ID,
"first_name": &userObject.FirstName,
"last_name": &userObject.LastName,
"email": &userObject.Email,
"avatar": &userObject.Avatar,
"location": &userObject.Location,
"bg_img": &userObject.BgImg,
}})
}
Model
// user.go
type User struct {
ID string `gorm:"primary_key:true"`
FirstName string `form:"first_name" json:"first_name,omitempty"`
LastName string `form:"last_name" json:"last_name,omitempty"`
Password string `form:"password" json:"password" bindind:"required"`
Email string `gorm:"type:varchar(110);unique_index" form:"email" json:"email,omitempty" binding:"required"`
Location string `form:"location" json:"location,omitempty"`
Avatar string `form:"avatar" json:"avatar,omitempty"`
BgImg string `form:"bg_img" json:"bg_img,omitempty"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}
func (repo *UserRepo) FindBy(params User) (User, error) {
var user User
err := repo.db.First(&user, "email = ?", params.Email).Error
fmt.Println(err)
if err != nil {
return user, err
}
return user, nil
}
I've tried doing the find in several different ways with no luck. I get a 401 response every time and the fmt.Prinlnt(err) in the model shows an record not found error.
So it turns out I needed to set the DeletedAt field to a null value. Under the hood, GORM automatically checks for a DeletedAt value. I.e SELECT * FROM users WHERE email = 'someone#gmail.com' AND deleted_at IS NULL. However, my DeletedAt fields were automatically being set to a blank date, which technically isn't NULL.
I added a struct...
type NullTime struct {
time.Time
Valid bool
}
Then updated my model...
type User struct {
ID string `gorm:"primary_key:true"`
FirstName string `form:"first_name" json:"first_name,omitempty"`
LastName string `form:"last_name" json:"last_name,omitempty"`
Password string `form:"password" json:"password" bindind:"required"`
Email string `gorm:"type:varchar(110);unique_index" form:"email" json:"email,omitempty" binding:"required"`
Location string `form:"location" json:"location,omitempty"`
Avatar string `form:"avatar" json:"avatar,omitempty"`
BgImg string `form:"bg_img" json:"bg_img,omitempty"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt NullTime
}
The convention in 'gorm' is to include the base model in your structs.
// Base Model definition from gomodel.go
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
// Your struct
type User struct {
gorm.Model
Name string
}
As you can see, 'gorm' expected a pointer to handle the NULL value.