How correctly create ForeignKey in GORM | Golang application? - postgresql

In PostgreSQL database I have 2 table:
CREATE TABLE WIDGET_TYPE(
WIDGET_TYPE_ID SERIAL PRIMARY KEY NOT NULL,
WIDGET_TYPE_NAME VARCHAR NOT NULL UNIQUE
);
CREATE TABLE QUESTION(
QUESTION_ID SERIAL PRIMARY KEY NOT NULL,
QUESTION_TEXT TEXT NOT NULL UNIQUE,
WIDGET_TYPE_ID INT NOT NULL,
FOREIGN KEY (WIDGET_TYPE_ID) REFERENCES WIDGET_TYPE (WIDGET_TYPE_ID)
);
As you can see each question has only one widget type for offerend answers.
After that step I am trying to design models in Golang application. I use GORM library for this task. I have problem when try to create new entry in question table. In the body of the POST request I send JSON object:
{
"question_text": "NEW QUESTION TEXT HERE",
"widget_type_id": 2
}
ERROR:
pq: insert or update on table "question" violates foreign key constraint "question_widget_type_id_fkey"
models.go:
package models
type WidgetType struct {
WidgetTypeID int `gorm:"primary_key" json:"widget_type_id"`
WidgetTypeName string `gorm:"not null;unique" json:"widget_type_name"`
}
func (WidgetType) TableName() string {
return "widget_type"
}
type Question struct {
QuestionID int `gorm:"primary_key" json:"question_id"`
QuestionText string `gorm:"not null;unique" json:"question_text"`
WidgetType WidgetType `gorm:"foreignkey:WidgetTypeID"`
WidgetTypeID uint
}
func (Question) TableName() string {
return "question"
}
handlers.go:
var CreateQuestion = func(responseWriter http.ResponseWriter, request *http.Request) {
question := models.Question{}
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(&question); err != nil {
utils.ResponseWithError(responseWriter, http.StatusBadRequest, err.Error())
return
}
defer request.Body.Close()
if err := database.DBGORM.Save(&question).Error; err != nil {
utils.ResponseWithError(responseWriter, http.StatusInternalServerError, err.Error())
return
}
utils.ResponseWithSuccess(responseWriter, http.StatusCreated, "The new entry successfully created.")
}
Where I make mistake?
I add built-in logger support of GORM. In console it show me next SQL statement:
INSERT INTO "question" ("question_text","widget_type_id") VALUES ('NEW QUESTION TEXT HERE',0) RETURNING "question"."question_id"
As you can see widget_type_id value 0. WHY?

Related

column "name" of relation "users" does not exist

Im trying to insert the user name to a posgres database but i got this error
Im Using heroku psql.
2022/03/01 03:52:54 pq: column "name" of relation "users" does not exist
m y code:
func createDB(db *sql.DB) {
// create users table if not exists
createTableUsers := "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY,name TEXT);"
statement, err := db.Prepare(createTableUsers)
if err != nil {
log.Fatal(err)
}
statement.Exec()
log.Println("Created table users")
}
func insertData(db *sql.DB, id string, name string) {
log.Println("Inserting data")
insertUser := "INSERT INTO users (name) VALUES ($1) RETURNING id;"
err := db.QueryRow(insertUser, name).Scan(&id)
if err != nil {
log.Println(err)
}
fmt.Println("New record ID is:", id)
}

How to insert Data in JSONB Field of Postgres using GORM

I have model like this in
model.go
type yourTableName struct {
Name string `gorm:"type:varchar(50)" json:"name"`
Email string `gorm:"type:varchar(50)" json:"email"`
FieldNameOfJsonb JSONB `gorm:"type:jsonb" json:"fieldnameofjsonb"`
}
I want to insert FieldNameOfJsonb as an array of object in postgres using GORM
Like given below
{
"name": " james",
"email": "james#gmail.com",
"FieldNameOfJsonb": [
{
"someField1": "value",
"someFiedl2": "somevalue",
},
{
"Field1": "value1",
"Fiedl2": "value2",
}
],
Just add this below code in Model.go (referenceLink)
import (
"errors"
"database/sql/driver"
"encoding/json"
)
// JSONB Interface for JSONB Field of yourTableName Table
type JSONB []interface{}
// Value Marshal
func (a JSONB) Value() (driver.Value, error) {
return json.Marshal(a)
}
// Scan Unmarshal
func (a *JSONB) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(b,&a)
}
-> reference Link for Marshal, Unmarshal
now you can insert data using DB.Create(&yourTableName)
I have answered a similar question in https://stackoverflow.com/a/71636216/13719636 .
The simplest way to use JSONB in Gorm is to use pgtype.JSONB.
Gorm uses pgx as it driver, and pgx has package called pgtype, which has type named pgtype.JSONB.
If you have already install pgx as Gorm instructed, you don't need install any other package.
This method should be the best practice since it using underlying driver and no custom code is needed.
type User struct {
gorm.Model
Data pgtype.JSONB `gorm:"type:jsonb;default:'[]';not null"`
}
Get value from DB
u := User{}
db.find(&u)
var data []string
err := u.Data.AssignTo(&data)
if err != nil {
t.Fatal(err)
}
Set value to DB
u := User{}
err := u.Data.Set([]string{"abc","def"})
if err != nil {
return
}
db.Updates(&u)
You can use gorm-jsonb package.

Unmarshal Json Array of object obtained from postgresql

I have a table in Postgres that is a Jsonb
Create Table Business(
id serial not null primary key,
id_category integer not null,
name varchar(50) not null,
owner varchar(200) not null,
coordinates jsonb not null,
reason varchar(300) not null,
foreign key(id_category) references Category(id)
);
as you can see i store the coordinates as a jsonb
ex:
Insert into Business(id_category, name, owner, coordinates, reason)
values
(1,'MyName','Owner', [{"latitude": 12.1268142, "longitude": -86.2754}]','Description')
the way that I extract the data and assign it is like this.
type Business struct {
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Owner string `json:"owner,omitempty"`
Category string `json:"category,omitempty"`
Departments []string `json:"departments,omitempty"`
Location []Coordinates `json:"location,omitempty"`
Reason string `json:"reason,omitempty"`
}
type Coordinates struct {
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
}
func (a Coordinates) Value() (driver.Value, error) {
return json.Marshal(a)
}
func (a *Coordinates) Scan(value []interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(b, &a)
}
However, I keep receiving this message.
sql: Scan error on column index 3, name "coordinates": unsupported
Scan, storing driver.Value type []uint8 into type *models.Coordinates
And the controller that I use to extract the information is this.
func (b *BusinessRepoImpl) Select() ([]models.Business, error) {
business_list := make([]models.Business, 0)
rows, err := b.Db.Query("SELECT business.id, business.name, business.owner, business.coordinates, business.reason_froggy, category.category FROM business INNER JOIN category on category.id = business.id_category group by business.id, business.name, business.owner, business.coordinates, business.reason_froggy, category.category")
if err != nil {
return business_list, err
}
for rows.Next() {
business := models.Business{}
err := rows.Scan(&business.ID, &business.Name, &business.Owner, &business.Location, &business.Reason, &business.Category)
if err != nil {
break
}
business_list = append(business_list, business)
}
err = rows.Err()
if err != nil {
return business_list, err
}
return business_list, nil
}
Can anyone please tell me how to solve this issue? Retrieve the json array of objects and assign it to the coordinates field inside Business.
1.
As you can see from the documentation the Scanner interface, to be satisfied, requires the method
Scan(src interface{}) error
But your *Coordinates type implements a different method
Scan(value []interface{}) error
The types interface{} and []interface{} are two very different things.
2.
The Scanner interface must be implemented on the type of the field which you want to pass as an argument to rows.Scan. That is, you've implemented your Scan method on *Coordinates but the type of the Location field is []Coordinates.
Again, same thing, the types *Coordinates and []Coordinates are two very different things.
So the solution is to implement the interface properly and on the proper type.
Note that since Go doesn't allow adding methods to unnamed types, and []Coordinates is an unnamed type, you need to declare a new type that you'll then use in place of []Coordinates.
type CoordinatesSlice []Coordinates
func (s *CoordinatesSlice) Scan(src interface{}) error {
switch v := src.(type) {
case []byte:
return json.Unmarshal(v, s)
case string:
return json.Unmarshal([]byte(v), s)
}
return errors.New("type assertion failed")
}
// ...
type Business struct {
// ...
Location CoordinatesSlice `json:"location,omitempty"`
// ...
}
NOTE
If the business location will always have only one pair of coordinates store into the db as a jsonb object and change the Location type from CoordinatesSlice to Coordinates and accordingly move the Scanner implementation from *CoordinatesSlice to *Coordinates.
I know that this solution is really unoptimized, but it was the only way that it works.
basically i have to obtain the json and then do an unmarshal into the Location attribute.
var location string = ""
if err := json.Unmarshal([]byte(location), &business.Location); err != nil { panic(err) }

Creating with gorms and many2many

I have the following models
type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_languages;"`
}
type Language struct {
gorm.Model
Name string
Users []User `gorm:"many2many:user_languages;"`
}
and for creating a language I do:
func CreateLanguage(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
language := models.Language{}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&language); err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
defer r.Body.Close()
if err := db.Save(&language).Error; err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusCreated, language)
}
when I check the database, I have the table Language filled with the language I created, but user_languages was not filled. I thought gorm was in charge of updating the intermediate table when you user gorm:"many2many:user_languages;", and the engine will figure out how to manage creations.
So question: how to manage creation with gorm when you have many2many relationships?
Gorm has a feature to auto save associations and its references from the struct. In your case you need to pass correct JSON object, for example if you pass:
{
"Name": "EN",
"Users": [
{
"ID": 1
}
]
}
Gorm will create new language with name "EN" and join it with the user row found by id 1 by creating new row in user_language table.
Read more about Gorm associations: http://gorm.io/docs/associations.html

Convert client UUID to SQL UUID

I'm using go and the package uuid to generate a uuid of type [16]byte. However when I try to insert that uuid into my postgres column of type uuid I get the error converting argument $1 type: unsupported type [16]uint8, a array. So apparently I should convert the uuid on the client before I insert it into the db. How should I do that? What type should I convert it to?
In short: What go data type will work with uuid in postgres?
Thanks to the link from #sberry, I found success. Here are snippets of the code for your benefit (with a PostgreSQL 9.5 database):
import (
"database/sql"
"net/http"
"github.com/google/uuid"
)
type Thing struct {
ID uuid.UUID `json:"-" sql:",type:uuid"`
Name string `json:"name"`
}
// For a database table created as such:
// CREATE TABLE things (
// id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
// name TEXT DEFAULT ''::text
// )
func selectThingssSQL() ([]Thing, error) {
things := make([]Thing, 0)
rows, err := db.Query("SELECT id, name FROM things")
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
t := &Thing{}
if err := rows.Scan(&t.ID, &t.Name); err != nil {
return nil, err
}
things = append(things, *t)
}
return things, nil
}