scan to a struct fields that some of them are pointers - postgresql

I'm writing a GO application and I'm trying to find an easy method to scan a row from the database to struct fields.
I use pgx to connect to a postgresql database
gqlgen generated this class:
type Profile struct {
Name string `json:"name"`
JoinedAt time.Time `json:"joined_at"`
Bio *string `json:"bio"`
}
and then I'm got the function to get the user profile from db:
func GetUserProfile(ctx context.Context, profileDir string) (*model.Profile, error) {
sqlQuery := `select name,joined_at::timestamptz,bio from foo where profile_dir=$1`
var profile model.Profile
if err := Connection.QueryRow(ctx, sqlQuery, profileDir).
Scan(&profile.Name, &profile.JoinedAt, &profile.Bio); err != nil {
return nil, err
} else {
return &profile, nil
}
}
now since Bio is a pointer, I need to create a variable who's not a pointer, scan to it and assign it's address after that to the struct:
var profile model.Profile
var mybio string
...
Connection.QueryRow(...).Scan(...,&mybio)
profile.Bio=&mybio
is there an easier way to scan a row to a struct that might have pointers ?

If Bio is already a pointer, you don't need to take an extra pointer in the Scan call:
profile := Profile{
Bio: new(string),
}
if err := Connection.QueryRow(ctx, sqlQuery, profileDir).
Scan(&profile.Name, &profile.JoinedAt, profile.Bio); err != nil {
return nil, err
} else {
return &profile, nil
}

Related

Get _id from mongoDB using gqlgen

I'm using Go and gqlgen to access my mongoDB database and was wondering how do I access the id field from the database? This is what I have currently and _id returns an empty string
type Post {
_id: ID!
title: String!
content: String!
comments: [Comment!]
author: User!
created: Date!
}
type Query {
post(_id: ID!): Post
...
}
func (r *queryResolver) Post(ctx context.Context, id string) (*model.Post, error) {
var post model.Post
_id, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
err = db.Posts.FindOne(context.TODO(), bson.D{{Key: "_id", Value: _id}}).Decode(&post)
if err != nil {
return nil, err
}
return &post, nil
}
The ID type in gqlgen creates a string, but _id in mongo is probably a primitive.ObjectId which can create issues depending on how you interact with mongo.
Better to set a bson tag as _id
consider setting the following struct to overwrite the gql generated flow. You can convert the id into a string using the Hex() method.
type Post struct{
ID primitive.ObjectID `bson:"_id" json:"id"`
title: string
content: string
comments: []Comment
author: User
created: time.Time
}
You might want to do this automatically if you have many structs with _ids. To avoid having to overwrite, you can implement a hook to autogenerate the bson tags for you
type Post {
id: ID!
title: String!
content: String!
comments: [Comment!]
author: User!
created: Date!
}
now a new directory in your file structure called "hooks" and create a new file "bson.go"
copy and paste the following
package main
import (
"fmt"
"os"
"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin/modelgen"
)
func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
for _, model := range b.Models {
for _, field := range model.Fields {
name := field.Name
if name == "id" {
name = "_id"
}
field.Tag += ` bson:"` + name + `"`
}
}
return b
}
func main() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
os.Exit(2)
}
p := modelgen.Plugin{
MutateHook: mutateHook,
}
err = api.Generate(cfg,
api.NoPlugins(),
api.AddPlugin(&p),
)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(3)
}
}
now in your main.go add this to the top
//go:generate go run hooks/bson.go
now when you run go generate not only will gqlgen do it's normal generation, but it will also add bson tags to all of our models. As well as any field with the name id to have a bson tag of _id
source : https://github.com/99designs/gqlgen/issues/865

How to marshal/unmarshal bson array with polymorphic struct with mongo-go-driver

I'm struggling to marshal/unmarshal bson array with polymorphic struct with mongo-go-driver。I plan to save a struct discriminator into the marshaled data, and write a custom UnmarshalBSONValue function to decode it according to the struct discriminator. But I don't know how to do it correctly.
package polymorphism
import (
"fmt"
"testing"
"code.byted.org/gopkg/pkg/testing/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
type INode interface {
GetName() string
}
type TypedNode struct {
NodeClass string
}
type Node struct {
TypedNode `bson:"inline"`
Name string
Children INodeList
}
func (n *Node) GetName() string {
return n.Name
}
type INodeList []INode
func (l *INodeList) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
fmt.Println("INodeList.UnmarshalBSONValue")
var arr []bson.Raw // 1. First, try to decode data as []bson.Raw
err := bson.Unmarshal(data, &arr) // error: cannot decode document into []bson.Raw
if err != nil {
fmt.Println(err)
return err
}
for _, item := range arr { // 2. Then, try to decode each bson.Raw as concrete Node according to `nodeclass`
class := item.Lookup("nodeclass").StringValue()
fmt.Printf("class: %v\n", class)
if class == "SubNode1" {
bin, err := bson.Marshal(item)
if err != nil {
return err
}
var sub1 SubNode1
err = bson.Unmarshal(bin, &sub1)
if err != nil {
return err
}
*l = append(*l, &sub1)
} else if class == "SubNode2" {
//...
}
}
return nil
}
type SubNode1 struct {
*Node `bson:"inline"`
FirstName string
LastName string
}
type SubNode2 struct {
*Node `bson:"inline"`
Extra string
}
With code above, I'm trying to decode INodeList data as []bson.Raw, then decode each bson.Raw as a concrete Node according to nodeclass. But it report error:
cannot decode document into []bson.Raw
at line
err := bson.Unmarshal(data, &arr).
So, how to do it correctly?
You need to pass a bson.Raw’s pointer to bson.Unmarshal(data, &arr) and then slice its value to an array of raw values like:
func (l *INodeList) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
fmt.Println("INodeList.UnmarshalBSONValue")
var raw bson.Raw // 1. First, try to decode data as bson.Raw
err := bson.Unmarshal(data, &raw)
if err != nil {
fmt.Println(err)
return err
}
// Slice the raw document to an array of valid raw values
rawNodes, err := raw.Values()
if err != nil {
return err
}
// 2. Then, try to decode each bson.Raw as concrete Node according to `nodeclass`
for _, rawNode := range rawNodes {
// Convert the raw node to a raw document in order to access its "nodeclass" field
d, ok := rawNode.DocumentOK()
if !ok {
return fmt.Errorf("raw node can't be converted to doc")
}
class := d.Lookup("nodeclass").StringValue()
// Decode the node's raw doc to the corresponding struct
var node INode
switch class {
case "SubNode1":
node = &SubNode1{}
case "SubNode2":
node = &SubNode2{}
//...
default:
// ...
}
bson.Unmarshal(d, node)
*l = append(*l, node)
}
return nil
}
Note that Note.Children must be a INodeList's pointer, and the inline fields must be a struct or a map (not a pointer):
type Node struct {
TypedNode `bson:",inline"`
Name string
Children *INodeList
}
type SubNode1 struct {
Node `bson:",inline"`
FirstName string
LastName string
}
type SubNode2 struct {
Node `bson:",inline"`
Extra string
}

How to handle duplicate unique index error?

I'm using MongoDB. Code to add data to the collection:
type User struct {
Firstname string `json:"firstname" bson:"firstname"`
Lastname *string `json:"lastname,omitempty" bson:"lastname"`
Username string `json:"username" bson:"username"`
RegistrationDate primitive.DateTime `json:"registrationDate" bson:"registrationData"`
LastLogin primitive.DateTime `json:"lastLogin" bson:"lastLogin"`
}
var client *mongo.Client
func AddUser(response http.ResponseWriter, request *http.Request) {
collection := client.Database("hattip").Collection("user")
var user User
_ = json.NewDecoder(request.Body).Decode(&user)
insertResult, err := collection.InsertOne(context.TODO(), user)
if err != nil {
// here i need to get the kind of error.
fmt.Println("Error on inserting new user", err)
response.WriteHeader(http.StatusPreconditionFailed)
} else {
fmt.Println(insertResult.InsertedID)
response.WriteHeader(http.StatusCreated)
}
}
func main() {
client = GetClient()
err := client.Ping(context.Background(), readpref.Primary())
if err != nil {
log.Fatal("Couldn't connect to the database", err)
} else {
log.Println("Connected!")
}
router := mux.NewRouter()
router.HandleFunc("/person", AddUser).Methods("POST")
err = http.ListenAndServe("127.0.0.1:8080", router)
if err == nil {
fmt.Println("Server is listening...")
} else {
fmt.Println(err.Error())
}
}
func GetClient() *mongo.Client {
clientOptions := options.Client().ApplyURI("mongodb://127.0.0.1:27017")
client, err := mongo.NewClient(clientOptions)
if err != nil {
log.Fatal(err)
}
err = client.Connect(context.Background())
if err != nil {
log.Fatal(err)
}
return client
}
If I add a record with a username that already exists in the database, I get -
Error on inserting new user multiple write errors: [{write errors:
[{E11000 duplicate key error collection: hattip.user index:
username_unique dup key: { username: "dd" }}]}, {}]
in the line fmt.Println("Error on inserting new user", err) The record with the string dd in the username field is already there, and the username field is a unique index.
I want to be sure that the error is exact E11000 error (a repeating collection of key errors).
So far i compare err to whole error string that appears on duplication of a unique field, but it's completely wrong. If there is a way to get error code from err object, or there are other ways to solve this problem?
Also, i found mgo package, but to use it properly i have to learn it, rewrite current code and so on, but honestly, it looks good:
if mgo.IsDup(err) {
err = errors.New("Duplicate name exists")
}
According to the driver docs, InsertOne may return a WriteException, so you can check if the error is a WriteException, and if it is so, then check the WriteErrors in it. Each WriteError contains an error code.
if we, ok:=err.(WriteException); ok {
for _,e:=range we.WriteErrors {
// check e.Code
}
}
You can write an IsDup based on this.

Create new struct and append it to array in a one to many relationship

I'm trying to append a new item to an array in a one-to-many relationship. The problem is that one of the IDs is always undefined and the model I want to append to does not get updated.
I have the following models:
type Station struct {
gorm.Model
Name string
Measurements []Measurement
PlantID uint64
Plant Plant
}
type Measurement struct {
ID uint64 `gorm:"primary_key"`
StationID uint64
TempSensor float32
LightSensor float32
HumiditySensor float32
CreatedAt time.Time
}
type Plant struct {
gorm.Model
Name string
}
This is the route to which I'm sending the post request:
/stations/:id/measurements
This is the current route handler that I have:
func CreateMeasurement(c *gin.Context) {
id := c.Params.ByName("id")
var station Station
if err := db.Where("id = ?", id).First(&station).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
var measurement Measurement
c.BindJSON(&measurement)
// Convert params string to uint
convertedID, err := strconv.ParseUint(id, 10, 64)
if err != nil {
fmt.Println(err)
}
measurement.StationID = convertedID
db.Model(&station).Association("Measurements").Append(&measurement)
db.Save(&station)
c.JSON(200, station)
}
}
Question: How can I create a new Measurement item and append it to the []Measurement array in a specific Station which is specified by the route parameters?
Solved the problem. It turns out there was some problem with the database table. Although I got auto migrate enable, there was a problem with some ids that were null.
Here is the working route:
func CreateMeasurement(c *gin.Context) {
id := c.Params.ByName("id")
var station Station
if err := db.Where("id = ?", id).First(&station).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
var measurement Measurement
c.BindJSON(&measurement)
db.Model(&station).Association("Measurements").Append(&measurement)
c.JSON(200, station)
}
}

Golang - MongoDB (mgo) retrieve inserted file (BSON not GridFS)

I am a newbie in Golang.
I am trying to retrieve a PDF file object I have inserted. I am not using GridFS, as the files that I would be storing are under 16 MB.
The object has been inserted (using load_file function) and the object ID I am seeing with the MongoDB visual client is ObjectId("554f98a400afc2dd3cbfb21b").
Unfortunately the file created on disk is of 0 kb.
Please advise how to correctly retrieve the inserted PDF object.
Thank you
package main
import (
"fmt"
"io/ioutil"
"gopkg.in/mgo.v2"
)
type Raw struct {
Kind byte
Data []byte
}
type RawDocElem struct {
Name string
Value Raw
}
func check(err error) {
if err != nil {
panic(err.Error())
}
}
func read_file_content(AFileName string) []byte {
file_contents, err := ioutil.ReadFile(AFileName)
check(err)
return file_contents
}
func save_fetched_file(AFileName RawDocElem) {
ioutil.WriteFile("fisier.pdf", AFileName.Value.Data, 0644)
fmt.Println("FileName:", AFileName.Name)
}
func load_file(AFileName string, ADatabaseName string, ACollection string) {
session, err := mgo.Dial("127.0.0.1,127.0.0.1")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
c := session.DB(ADatabaseName).C(ACollection)
the_obj_to_insert := Raw{Kind: 0x00, Data: read_file_content(AFileName)}
err = c.Database.C(ACollection).Insert(&the_obj_to_insert)
check(err)
}
func get_file(ADatabaseName string, ACollection string, The_ID string) RawDocElem {
session, err := mgo.Dial("127.0.0.1,127.0.0.1")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
c := session.DB(ADatabaseName).C(ACollection)
result := RawDocElem{}
err = c.FindId(The_ID).One(&result)
return result
}
func main() {
//f_name := "Shortcuts.pdf"
db_name := "teste"
the_collection := "ColectiaDeFisiere"
//load_file(f_name, db_name, the_collection)
fmt.Sprintf(`ObjectIdHex("%x")`, string("554f98a400afc2dd3cbfb21b"))
save_fetched_file(get_file(db_name, the_collection, fmt.Sprintf(`ObjectIdHex("%x")`, string("554f98a400afc2dd3cbfb21b"))))
}
I think your way to build the object ID is wrong. Furthermore, you forgot to test the error following the FindId call.
I would try to import the bson package, and build the object ID using something like:
hexString := "554f98a400afc2dd3cbfb21b" //24 digits hexadecimal string
objid := bson.ObjectIdHex(hexString)
...
err = c.FindId(objid).One(&result)
if err == nil {
// Do something with result
...
} else {
// Error
...
}