My goal is to get some filtered records from database. Filtration is based on a struct which depends on another struct:
type Group struct {
ID primitive.ObjectID
Name string
}
type Role struct {
ID primitive.ObjectID
Name string
Description string
Groups []*group.Group
}
I create an object of Role struct from URL query parameters:
var roleWP Role
if r.URL.Query().Has("name") {
name := r.URL.Query().Get("name")
roleWP.Name = name
}
if r.URL.Query().Has("description") {
description := r.URL.Query().Get("description")
roleWP.Description = description
}
if r.URL.Query().Has("groups") {
//How would look groups parameter?
}
Filling name and description fields of Role struct is pretty simple. The whole url would be: myhost/roles?name=rolename&description=roledescription
But how would look url if I want to pass data for Group struct? Is it possible to pass data as a json object in query parameter? Also, I want to mention that groups field in Role is an array. My ideal dummy url would look like: myhost/roles?name=rolename&description=roledescription&groups={name:groupname1}&groups={name:groupname2}
Loop through the groups, split on :, create group and append to slice:
roleWP := Role{
Name: r.FormValue("name"),
Description: r.FormValue("description"),
}
for _, g := range r.Form["groups"] {
g = strings.TrimPrefix(g, "{")
g = strings.TrimSuffix(g, "}")
i := strings.Index(g, ":")
if i < 0 {
// handle error
}
roleWP.Groups = append(roleWP.Groups, &Group{g[:i], g[i+1:]})
}
Here's how to use JSON instead of OP's ideal format:
roleWP := Role{
Name: r.FormValue("name"),
Description: r.FormValue("description"),
}
for _, s := range r.Form["groups"] {
var g Group
err := json.Unmarshal([]byte(s), &v)
if err != nil {
// handle error
}
roleWP.Groups = append(roleWP.Groups, &g)
}
Related
How to update the document with non zero values only. As example I didn't received any value for status and Struct has only two values to be updated. So it should only update those 2 values and skip zero/null values. But as given below it's updating it to zero/null/""
type Product struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Status int `json:"status" bson:"status"`
DisplayName string `json:"displayName" bson:"display_name"`
Text string `json:"text" bson:"text"`
}
I have tried the following up it's overriding the status value to 0 if no value is passed for it.
opts := options.Update().SetUpsert(false)
filter := bson.D{primitive.E{Key: "_id", Value: product.ID}}
update := bson.D{{"$set", bson.D{{"status", product.Status}, bson.D{{"text",product.Text}, {"display_name", product.DisplayName}}}}
_, err := db.Collection("product").UpdateOne(context.TODO(), filter, update, opts)
How to achieve this cleanly without ifs. For any struct in Service.
First, your update document should not contain an embedded document if Product is the Go struct that models your documents. It should be:
update := bson.D{
{"$set", bson.D{
{"status", product.Status},
{"text", product.Text},
{"display_name", product.DisplayName},
}},
}
Now on to your issue. You explicitly tell in the update document to set them to their zero value, so that's what MongoDB does.
If you don't want to set zero values, don't add them to the update document. Build your update document like this:
setDoc := bson.D{}
if product.Status != 0 {
setDoc = append(setDoc, bson.E{"status", product.Status})
}
if product.Text != "" {
setDoc = append(setDoc, bson.E{"text", product.Text})
}
if product.DisplayName != "" {
setDoc = append(setDoc, bson.E{"display_name", product.DisplayName})
}
update := bson.D{{"$set", setDoc}}
Note that you can achieve the same if you use the ,omitempty BSON tag option and use / pass a Product struct value for the setDoc:
type Product struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Status int `json:"status" bson:"status,omitempty"`
DisplayName string `json:"displayName" bson:"display_name,omitempty"`
Text string `json:"text" bson:"text,omitempty"`
}
And then simply:
update := bson.D{{"$set", product}}
I have a function for comparing two structs and making a bson document as input to mongodb updateOne()
Example struct format
type event struct {
...
Name string
StartTime int32
...
}
Diff function, please ignore that I have not checked for no difference yet.
func diffEvent(e event, u event) (bson.M, error) {
newValues := bson.M{}
if e.Name != u.Name {
newValues["name"] = u.Name
}
if e.StartTime != u.StartTime {
newValues["starttime"] = u.StartTime
}
...
return bson.M{"$set": newValues}, nil
}
Then I generated a test function like so:
func Test_diffEvent(t *testing.T) {
type args struct {
e event
u event
}
tests := []struct {
name string
args args
want bson.M
wantErr bool
}{
{
name: "update startime",
args: args{
e: event{StartTime: 1},
u: event{StartTime: 2},
},
want: bson.M{"$set": bson.M{"starttime": 2}},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := diffEvent(tt.args.e, tt.args.u)
if (err != nil) != tt.wantErr {
t.Errorf("diffEvent() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("diffEvent() = %v, want %v", got, tt.want)
}
})
}
}
This fails with a
--- FAIL: Test_diffEvent/update_startime (0.00s)
models_test.go:582: diffEvent() = map[$set:map[starttime:2]], want map[$set:map[starttime:2]]
For me this seem to be the same. I have played around with this and bool fields, string fields, enum fields, and fields as struct or fields as arrays of structs seems to work fine with deepequal, but it gives an error for int32 fields.
As a go beginner; what am I missing here? I would assume that if bool/string works then int32 would too.
This:
bson.M{"starttime": 2}
Sets the "starttime" key to the value of the literal 2. 2 is an untyped integer constant, and since no type is provided, its default type will be used which is int.
And 2 values stored in interface values are only equal if the dynamic value stored in them have identical type and value. So a value 2 with int type cannot be equal to a value 2 of type int32.
Use explicit type to tell you want to specify a value of int32 type:
bson.M{"starttime": int32(2)}
I am trying to insert a struct in mongo database.
type SecretsStruct struct {
UserID string `bson:"userid" json:"userid"`
secretOne string `bson:"secret_one" json:secret_one`
secretTwo string `bson:"secret_two" json:secret_two`
secretThree string `bson:"secret_three" json:secret_three`
}
func (c *SecretsStruct) SetSecrets(userId string, encryptedKeys
[][]byte){
c.UserID = userId
c.secretOne = hex.EncodeToString(encryptedKeys[0])
c.secretTwo = hex.EncodeToString(encryptedKeys[1])
c.secretThree = hex.EncodeToString(encryptedKeys[2])
log.Printf("This is the c %s", c)
}
g := SecretsStruct{}
g.SetSecrets(userStruct.UserID, encryptedKeys)
err = secretCollection.Insert(g)
if err != nil {
panic(err)
}
I have tried inserting the byte arrays corresponding to the secrets but of no help. The result which gets populated to the corresponding insertion operation is :
{'_id': ObjectId('5b80117c118c660aaa0c87c2'),
'userid': 'eb19d220-ef13-43aa-8a7f-f78637718000'}
On the other hand, if I try to insert same data with a map but without struct.
secretCollection.Insert(bson.M{"userid": userStruct.UserID,
"secret_one": encryptedKeys[0],
"secret_two": encryptedKeys[1],
"secret_three": encryptedKeys[2]})
The insertion operation executes successfully.
You have to export your struct fields, so that another package (in this case mgo) can access them:
type SecretsStruct struct {
UserID string `bson:"userid" json:"userid"`
SecretOne string `bson:"secret_one" json:secret_one`
SecretTwo string `bson:"secret_two" json:secret_two`
SecretThree string `bson:"secret_three" json:secret_three`
}
If I have a table that returns something like:
id: 1
names: {Jim, Bob, Sam}
names is a varchar array.
How do I scan that back into a []string in Go?
I'm using lib/pg
Right now I have something like
rows, err := models.Db.Query("SELECT pKey, names FROM foo")
for rows.Next() {
var pKey int
var names []string
err = rows.Scan(&pKey, &names)
}
I keep getting:
panic: sql: Scan error on column index 1: unsupported Scan, storing driver.Value type []uint8 into type *[]string
It looks like I need to use StringArray
https://godoc.org/github.com/lib/pq#StringArray
But, I think I'm too new to Go to understand exactly how to use:
func (a *StringArray) Scan(src interface{})
You are right, you can use StringArray but you don't need to call the
func (a *StringArray) Scan(src interface{})
method yourself, this will be called automatically by rows.Scan when you pass it anything that implements the Scanner interface.
So what you need to do is to convert your []string to *StringArray and pass that to rows.Scan, like so:
rows, err := models.Db.Query("SELECT pKey, names FROM foo")
for rows.Next() {
var pKey int
var names []string
err = rows.Scan(&pKey, (*pq.StringArray)(&names))
}
Long Story Short, use like this to convert pgSQL array to GO array, here 5th column is coming as a array :
var _temp3 []string
for rows.Next() {
// ScanRows scan a row into temp_tbl
err := rows.Scan(&_temp, &_temp0, &_temp1, &_temp2, pq.Array(&_temp3))
In detail :
To insert a row that contains an array value, use the pq.Array function like this:
// "ins" is the SQL insert statement
ins := "INSERT INTO posts (title, tags) VALUES ($1, $2)"
// "tags" is the list of tags, as a string slice
tags := []string{"go", "goroutines", "queues"}
// the pq.Array function is the secret sauce
_, err = db.Exec(ins, "Job Queues in Go", pq.Array(tags))
To read a Postgres array value into a Go slice, use:
func getTags(db *sql.DB, title string) (tags []string) {
// the select query, returning 1 column of array type
sel := "SELECT tags FROM posts WHERE title=$1"
// wrap the output parameter in pq.Array for receiving into it
if err := db.QueryRow(sel, title).Scan(pq.Array(&tags)); err != nil {
log.Fatal(err)
}
return
}
Note: that in lib/pq, only slices of certain Go types may be passed to pq.Array().
Another example in which varchar array in pgSQL in generated at runtime in 5th column, like :
--> predefined_allow false admin iam.create {secrets,configMap}
I converted this as,
Q := "SELECT ar.policy_name, ar.allow, ar.role_name, pro.operation_name, ARRAY_AGG(pro.resource_id) as resources FROM iam.authorization_rules ar LEFT JOIN iam.policy_rules_by_operation pro ON pro.id = ar.operation_id GROUP BY ar.policy_name, ar.allow, ar.role_name, pro.operation_name;"
tx := g.db.Raw(Q)
rows, _ := tx.Rows()
defer rows.Close()
var _temp string
var _temp0 bool
var _temp1 string
var _temp2 string
var _temp3 []string
for rows.Next() {
// ScanRows scan a row into temp_tbl
err := rows.Scan(&_temp, &_temp0, &_temp1, &_temp2, pq.Array(&_temp3))
if err != nil {
return nil, err
}
fmt.Println("Query Executed...........\n", _temp, _temp0, _temp1, _temp2, _temp3)
}
Output :
Query Executed...........
predefined_allow false admin iam.create [secrets configMap]
I have a GOLANG struct as follows:
type OrgWhoAmI struct {
FriendlyName string `json:"friendlyName"`
RedemptionCode string `json:"redemptionCode"`
StartUrls []StartUrl `json:"startUrls"`
Status string `json:"status"`
Children []OrgChildren `json:"childrenReemptionCodes"`
}
type StartUrl struct {
DisplayName string `json:"displayName"`
URL string `json:"url"`
}
type OrgChildren struct {
FriendlyName string `json:"childFriendlyName"`
RedemptionCode string `json:"childRedemptionCode"`
}
I've created and successfully inserted records into a MongoDB collection (as I can see the results by querying Mongo with the CLI mongo program) - but when I query with MGO as follows, I get nothing:
func main() {
session, sessionErr := mgo.Dial("localhost")
defer session.Close()
// Query All
collection := session.DB("OrgData").C("orgWhoAmI")
var results []OrgWhoAmI
err = collection.Find(bson.M{}).All(&results)
if err != nil {
panic(err)
}
for _, res := range results {
fmt.Printf("Result: %s|%s\n", res.FriendlyName, res.RedemptionCode)
}
}
The results printed are:
Result: |
Result: |
Result: |
Result: |
If I ask for the count for records, I get the correct number, but all values for all fields are blank. Not sure what I'm missing here.
If you aren't creating them in go, it's probably not serializing the key names for you properly. The default for bson is to lowercase the keys, so you need to specify it if you want something else. Also note that you have a typo in OrgWhoAmI for json:"childrenReemptionCodes" (should be Redemption, I'm guessing). You can specify both bson and json separately if you want them to be different.
type OrgWhoAmI struct {
FriendlyName string `bson:"friendlyName" json:"friendlyName"`
RedemptionCode string `bson:"redemptionCode" json:"redemptionCode"`
StartUrls []StartUrl `bson:"startUrls" json:"startUrls"`
Status string `bson:"status" json:"status"`
Children []OrgChildren `bson:"childrenRedemptionCodes" json:"childrenRedemptionCodes"`
}
type StartUrl struct {
DisplayName string `bson:"displayName" json:"displayName"`
URL string `bson:"url" json:"url"`
}
type OrgChildren struct {
FriendlyName string `bson:"childFriendlyName" json:"childFriendlyName"`
RedemptionCode string `bson:"childRedemptionCode" json:"childRedemptionCode"`
}