Read flattened entity from cloud datastore in golang - go

func (db *dataStore) AddAcceptance(ctx context.Context, req *acceptance.PolicyAcceptance) (uint64, error) {
accpKey := datastore.IncompleteKey("Acceptance", nil)
key, err := db.Put(context.Background(), accpKey, req);
if err != nil {
log.Fatalf("Failed to save Acceptance: %v", err)
}
accpKey = key
val := uint64(accpKey.ID)
return val, err
}
type PolicyAcceptance struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
PolicyNumber int64 `protobuf:"varint,2,opt,name=policyNumber" json:"policyNumber,omitempty"`
Version string `protobuf:"bytes,3,opt,name=version" json:"version,omitempty"`
SignerData *SignerData `protobuf:"bytes,4,opt,name=signerData" json:"signerData,omitempty" datastore:",flatten"`
GroupID int64 `protobuf:"varint,5,opt,name=groupID" json:"groupID,omitempty"`
LocationID int64 `protobuf:"varint,6,opt,name=locationID" json:"locationID,omitempty"`
BusinessId int64 `protobuf:"varint,7,opt,name=businessId" json:"businessId,omitempty"`
AcceptedDate *google_protobuf.Timestamp `protobuf:"bytes,8,opt,name=acceptedDate" json:"acceptedDate,omitempty" datastore:",flatten"`
IssuerName string `protobuf:"bytes,9,opt,name=issuerName" json:"issuerName,omitempty"`
Place string `protobuf:"bytes,10,opt,name=place" json:"place,omitempty"`
}
type SignerData struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Email string `protobuf:"bytes,2,opt,name=email" json:"email,omitempty"`
Type string `protobuf:"bytes,3,opt,name=type" json:"type,omitempty"`
Id int64 `protobuf:"varint,4,opt,name=id" json:"id,omitempty"`
}
datastore:",flatten" saves data as flattened in data store. The property names becomes flattened with . like SignerData.Id as property name but when it's read from data store, how can I map it back to struct? It fails throwing an error like:
SignerData.Id could not be found as a key in struct. Error: No such
struct field.
func (db *dataStore) GetAcceptanceBySignerData(ctx context.Context, req *acceptance.SignerData) (*acceptance.ListOfPolicyAcceptance, error) {
query := datastore.NewQuery("Acceptance").Filter("SignerData.Id =", req.Id)
var accpArr acceptance.ListOfPolicyAcceptance
var err error
it := db.Run(ctx, query)
for {
var accept acceptance.PolicyAcceptance
_, err := it.Next(&accept)
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("Error fetching : %v", err)
}
accpArr.AcceptanceList = append(accpArr.AcceptanceList, &accept)
}
return &accpArr, err
}

Related

httptest.NewRequest issues with query parameters

I'm using the GIN framework with a Postgres DB and GORM as an ORM.
One of the routes accepts query parameters. When I search for this in my browser, I get the expected results: an array of json objects. Here is the route:
/artworks/?limit=10&last_id=1
However, when I try to test the handler used by that route, I get the following error:
routes_test.go:184: [ERROR] Unable to unmarshal data to artworks: json: cannot unmarshal object into Go value of type []models.Artwork
The query that the ORM is trying to run in the test function is the folowing:
SELECT * FROM "artwork_migrate_artwork" WHERE id = ''
So when I run the request in the browser, it properly pulls the query parameters and then the ORM runs the proper sql query. But when using httptest.NewRequest it seems like the query parameters are not used.
Here is my test function:
func TestGetArtworks(t *testing.T) {
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=%s", env_var.Host, env_var.User, env_var.Password, env_var.DBname, env_var.Port, env_var.SSLMODE, env_var.TimeZone)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect to db")
}
route := "/artworks/"
handler := handlers.GetArtwork(db)
router := setupGetRouter(handler, route)
writer := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/artworks/?limit=10&last_id=1", nil)
fmt.Println(req)
router.ServeHTTP(writer, req)
assert.Equal(t, 200, writer.Code)
data, err := ioutil.ReadAll(writer.Body)
if err != nil {
t.Errorf("\u001b[31m[Error] Unable to read writer.Body: %s", err)
}
// no body can be unmarshalled
fmt.Println("Here is the body:", writer.Body.String())
var artworks []models.Artwork
if err := json.Unmarshal(data, &artworks); err != nil {
t.Errorf("\u001b[31m[ERROR] Unable to unmarshal data to artworks: %s", err)
}
assert.Equal(t, 10, len(artworks))
}
Here is my route handler:
func GetArtworks(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
limit, err := strconv.Atoi(c.Query("limit"))
if err != nil {
panic(err)
}
last_id := c.Query("last_id")
var artworks []models.Artwork
db.Where("id > ?", last_id).Limit(limit).Find(&artworks)
c.JSON(http.StatusOK, artworks)
}
}
router.GET("/artworks", han.GetArtworks(db))
Here is the model struct:
type Artwork struct {
ID int `json:"id"`
Title string `json:"title"`
Nationality string `json:"nationality"`
Artist_Bio string `json:"artist_bio"`
Desc string `json:"desc"`
Culture string `json:"culture"`
Gender string `json:"gender"`
Nation string `json:"nation"`
Medium string `json:"medium"`
Date_of_Release string `json:"date_of_release"`
Image string `json:"image"`
Image_Small string `json:"image_small"`
Last_Modified time.Time `json:"last_modified"`
Artist_ID int `json:"artist_id"`
Source_ID int `json:"source_id"`
}
#Brits is correct: It was due to a misspelled handler
handler := handlers.GetArtwork(db)
should have been handler := handlers.GetArtworks(db)

How to set slice interface values with reflection

I would like to build a function that takes a generic pointer array and fill that list based on mongo results.
I don't know how to set the value I got from mongo into my pointer array. In the below attempt, program panics with following error : reflect.Set: value of type []interface {} is not assignable to type []Person
When I print total / documents found, it corresponds to what I am expecting. So I think question is about reflection.
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
var mongoResp struct {
Total int `bson:"total"`
Documents interface{} `bson:"documents"`
}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
receiverValue := reflect.ValueOf(receiver)
docs := []interface{}(mongoResp.Documents.(primitive.A))
receiverValue.Elem().Set(reflect.ValueOf(docs))
return mongoResp.Total, nil
}
type Person struct {
Name string `bson:"name"`
}
func main() {
var persons []Person
count, err := getListWithCount(context.Background(), &persons)
if err != nil {
log.Fatal(err)
}
fmt.Println(count)
fmt.Println(persons)
}
You should be able to decode first into bson.RawValue and then Unmarshal it into the receiver.
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
var mongoResp struct {
Total int `bson:"total"`
Documents bson.RawValue `bson:"documents"`
}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
if err := mongoResp.Documents.Unmarshal(receiver); err != nil {
return 0, err
}
return mongoResp.Total, nil
}
You can also implement it as a custom bson.Unmarshaler.
type MongoResp struct {
Total int `bson:"total"`
Documents interface{} `bson:"documents"`
}
func (r *MongoResp) UnmarshalBSON(data []byte) error {
var temp struct {
Total int `bson:"total"`
Documents bson.RawValue `bson:"documents"`
}
if err := bson.Unmarshal(data, &temp); err != nil {
return err
}
r.Total = temp.Total
return temp.Documents.Unmarshal(r.Documents)
}
With that you would use it in the function like so:
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
mongoResp := MongoResp{Documents: receiver}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
return mongoResp.Total, nil
}
Dynamically create a struct type that matches the queried document. See commentary below for details.
func getListWithCount(receiver interface{}) (int, error) {
dst := reflect.ValueOf(receiver).Elem()
// Your mongo query here
// Create a struct type that matches the document.
doct := reflect.StructOf([]reflect.StructField{
reflect.StructField{Name: "Total", Type: reflect.TypeOf(0), Tag: `bson:"total"`},
reflect.StructField{Name: "Documents", Type: dst.Type(), Tag: `bson:"documents"`},
})
// Decode to a value of the type.
docp := reflect.New(doct)
if err := cursor.Decode(docp.Interface()); err != nil {
return 0, err
}
docv := docp.Elem()
// Copy the Documents field to *receiver.
dst.Set(docv.Field(1))
// Return the total
return docv.Field(0).Interface().(int), nil
}
there is no need to use reflect here, you can decode it directly to your Person slices
func getPersons(ctx context.Context, coll *mongo.Collection, results interface{}) error {
cur, err := coll.Find(ctx, bson.D{})
if err != nil {
return err
}
err = cur.All(ctx, results)
if err != nil {
return err
}
return nil
}
and the len is the count of the results.
err = getPersons(ctx, coll, &persons)
require.NoError(t, err)
t.Logf("Got %d persons: %v", len(persons), persons)
see https://gist.github.com/xingyongtao/459f92490bdcbf7d5afe9f5d1ae6c04a

Bigquery Entity Tag

When I loaded my data from Cloud Storage into Bigquery, the click_url field turned into Record type when get created even though it's a string (maybe because of the noindex not sure tho). When I try to insert the data into BigQuery using Inserter. I got this error message:
Cannot convert std::string to a record field:optional .Msg_0_CLOUD_QUERY_TABLE.Msg_1_CLOUD_QUERY_TABLE_click_url click_url = 1
Table in bigquery:
Schema:
Here's the code:
type Product struct {
Name string `datastore:"name" bigquery:"name"`
ClickUrl string `datastore:"click_url,noindex" bigquery:"click_url"`
DateAdded time.Time `datastore:"date_added" bigquery:"date_added"`
}
func insertRows(data interface{}) error {
projectID := "my-project-id"
datasetID := "mydataset"
tableID := "mytable"
ctx := context.Background()
client, err := bigquery.NewClient(ctx, projectID)
if err != nil {
return fmt.Errorf("bigquery.NewClient: %v", err)
}
defer client.Close()
inserter := client.Dataset(datasetID).Table(tableID).Inserter()
if err := inserter.Put(ctx, data); err != nil {
return err
}
return nil
}
func main() {
product := Product{"product_name", "click_url", "date_added_value"} // Example data from datastore
if err := insertRows(product); err != nil {
fmt.Println(err)
}
}
What should I put on the entity tag "bigquery:click_url" to make this work?
Because the ClickUrl in BigQuery is a STRUCT type, which has key-value pairs.
Maybe try this?
type Product struct {
Name string `datastore:"name" bigquery:"name"`
ClickUrl struct {
String string
Text string
Provided string
} `datastore:"click_url,noindex" bigquery:"click_url"`
DateAdded time.Time `datastore:"date_added" bigquery:"date_added"`
}

How To Use Embedding To Get Data From A Table

I am a PHP programmer and I have just learned Golang for weeks. I am writing REST API to get post information from my 'posts' table. I have the FindPostByID method in posts_controller. This controller uses Post struct which is model
func (p *Post) FindPostByID(db *gorm.DB, pid uint64) (*Post, error) {
var err error
err = db.Debug().Model(&Post{}).Where("id = ?", pid).Take(&p).Error
if err != nil {
return &Post{}, err
}
if p.ID != 0 {
err = db.Debug().Model(&User{}).Where("id = ?", p.AuthorID).Take(&p.Author).Error
if err != nil {
return &Post{}, err
}
}
return p, nil
}
But now I want to add method FindByID into parent struct, such as ParentModel because when I have articles_controller I can use FindByID method in parent struct to get an article info. So I have the following code in parent struct
type ParentModel struct {
}
func (m *ParentModel) FindByID(db *gorm.DB, uid uint64) (*ParentModel, error) {
var err error
err = db.Debug().Model(ParentModel{}).Where("id = ?", uid).Take(&m).Error
if err != nil {
return &ParentModel{}, err
}
if gorm.IsRecordNotFoundError(err) {
return &ParentModel{}, errors.New("User Not Found")
}
return m, err
}
And I change in the Post struct like following:
type Post struct {
ParentModel
ID uint64 `gorm:"primary_key;auto_increment" json:"id"`
Title string `gorm:"size:255;not null;unique" json:"title"`
Content string `gorm:"size:255;not null;" json:"content"`
Author User `json:"author"`
AuthorID uint32 `sql:"type:int REFERENCES users(id)" json:"author_id"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
I change FindPostByID method in posts_controller as well:
func (server *Server) GetPost(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pid, err := strconv.ParseUint(vars["id"], 10, 64)
if err != nil {
responses.ERROR(w, http.StatusBadRequest, err)
return
}
post := models.Post{}
postReceived, err := post.FindByID(server.DB, pid)
if err != nil {
responses.ERROR(w, http.StatusInternalServerError, err)
return
}
responses.JSON(w, http.StatusOK, postReceived)
}
When I run my program, have the err: Table 'golang_rest_api.parent_models' doesn't exist.
How I can do to use inheritance method like PHP language
The error Table 'golang_rest_api.parent_models' doesn't exist means that no table named parent_models exists in your database.
This is because gorm by default uses the pluralised camelcase name of your struct, which you supplied in gorm's Model() method, as its table's name.
Gorm provides in-built support for many kinds of associations which you can find here. I would also like to ask you to complete your ParentModel struct's definition. It's empty right now.

Convert string to custom struct Go

Hi I have written code to read data from MongoDB.
It worked but I don't know how to convert string result to my custom struct.
Here is my code
type UserSSO struct {
Username string `json:"username"`
Password string `json:"password"`
Lastname string `json:"lastname"`
Useremail string `json:"useremail"`
Usertel string `json:"usertel"`
Userdate string `json:"userdate"`
Userstatus string `json:"userstatus"`
Userparentid string `json:"userparentid"`
Comid string `json:"comid"`
Comdepartment string `json:"comdepartment"`
Usercode string `json:"usercode"`
Usertype string `json:"usertype"`
}
func GetInfomationChildOfNode(node string) (err error, info string) {
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://casuser:Mellon#222.255.102.145:27017/users"))
if err != nil {
return err, ""
}
defer client.Disconnect(ctx)
database := client.Database("users")
users := database.Collection("users")
matchStage := bson.D{{"$match", bson.D{{"username", node}}}}
graphStage := bson.D{{"$graphLookup", bson.D{{"from", "users"}, {"startWith", "$username"}, {"connectFromField", "username"}, {"connectToField", "userparentid"}, {"as", "descendants"}}}}
unWind := bson.D{{"$unwind", "$descendants"}}
replaceRoot := bson.D{{"$replaceRoot", bson.D{{"newRoot", "$descendants"}}}}
proJect := bson.D{{"$project", bson.D{{"descendants", 0}}}}
showInfoCursor, err := users.Aggregate(ctx, mongo.Pipeline{matchStage, graphStage, unWind, replaceRoot, proJect})
if err != nil {
return err, ""
}
var showsWithInfo []bson.M
if err = showInfoCursor.All(ctx, &showsWithInfo); err != nil {
return err, ""
}
data, _ := json.Marshal(showsWithInfo)
stringData := string(data)
fmt.Println(stringData)
return nil, stringData
}
And here is my string result output
[{"_id":"5ee0ac96653a000065005c03","comdepartment":"KHOA_DIEN","comid":"DHBK","lastname":"KHOA_DIEN","password":"123456","usercode":"DHBK_0002","userdate":"2020-05-05","useremail":"KHOA_DIEN#edu.com.vn","username":"KHOA_DIEN","userparentid":"DHBK","userstatus":"ACTIVE","usertel":"0907111002","usertype":"USER_COM"},{"_id":"5ee0ac96653a000065005c04","comdepartment":"KHOA_XD","comid":"DHBK","lastname":"KHOA_XD","password":"123456","usercode":"DHBK_0003","userdate":"2020-05-05","useremail":"KHOA_XD#edu.com.vn","username":"KHOA_XD","userparentid":"DHBK","userstatus":"DISABLE","usertel":"0907111003","usertype":"USER_COM"},{"_id":"5ee0ac96653a000065005c08","comdepartment":"KHOA_DIEN","comid":"DHBK","lastname":"BOMON_HETHONG","password":"123456","usercode":"DHBK_0007","userdate":"2020-05-05","useremail":"BOMON_HETHONG#edu.com.vn","username":"BOMON_HETHONG","userparentid":"KHOA_DIEN","userstatus":"ACTIVE","usertel":"0907111007","usertype":"USER_COM"},{"_id":"5ee0ac96653a000065005c09","comdepartment":"KHOA_XD","comid":"DHBK","lastname":"BOMON1_XD","password":"123456","usercode":"DHBK_0008","userdate":"2020-05-05","useremail":"BOMON1_XD#edu.com.vn","username":"BOMON1_XD","userparentid":"KHOA_XD","userstatus":"DISABLE","usertel":"0907111008","usertype":"USER_COM"},{"_id":"5ee0ac96653a000065005c0a","comdepartment":"KHOA_XD","comid":"DHBK","lastname":"BOMON2_XD","password":"123456","usercode":"DHBK_0009","userdate":"2020-05-05","useremail":"BOMON2_XD#edu.com.vn","username":"BOMON2_XD","userparentid":"KHOA_XD","userstatus":"DISABLE","usertel":"0907111009","usertype":"USER_COM"},{"_id":"5ee0ac96653a000065005c0b","comdepartment":"KHOA_XD","comid":"DHBK","lastname":"BOMON3_XD","password":"123456","usercode":"DHBK_0010","userdate":"2020-05-05","useremail":"BOMON3_XD#edu.com.vn","username":"BOMON3_XD","userparentid":"KHOA_XD","userstatus":"DISABLE","usertel":"0907111010","usertype":"USER_COM"},{"_id":"5ee0ac96653a000065005c05","comdepartment":"KHOA_CNTT","comid":"DHBK","lastname":"KHOA_CNTT","password":"123456","usercode":"DHBK_0004","userdate":"2020-05-05","useremail":"KHOA_CNTT#edu.com.vn","username":"KHOA_CNTT","userparentid":"DHBK","userstatus":"ACTIVE","usertel":"0907111004","usertype":"USER_COM"},{"_id":"5ee0ac96653a000065005c06","comdepartment":"KHOA_DIEN","comid":"DHBK","lastname":"BOMON_TUDONG","password":"123456","usercode":"DHBK_0005","userdate":"2020-05-05","useremail":"BOMON_TUDONG#edu.com.vn","username":"BOMON_TUDONG","userparentid":"KHOA_DIEN","userstatus":"ACTIVE","usertel":"0907111005","usertype":"USER_COM"}]
In addition, I used this code to convert bson.M to struct, but it fail
var userSSO UserSSO
bsonBytes, _ := bson.Marshal(showsWithInfo)
bson.Unmarshal(bsonBytes, &userSSO)
fmt.Println(userSSO)
Result is { }
Thank you in advance.
var userSSO UserSSO
bsonBytes, _ := bson.Marshal(showsWithInfo)
bson.Unmarshal(bsonBytes, &userSSO)
fmt.Println(userSSO)
You are marshalling with bson and trying to unmarshal with json. Both are different formats and hence this will not work.
You can do something like this below to unmarshal a map[string]interface{} (which is bascically bson.M) into a struct
package main
import (
"encoding/json"
"fmt"
"go.mongodb.org/mongo-driver/bson"
)
type data struct {
StringField string `json:"stringField"`
IntField int `json:"intField,string"`
}
func main() {
i := []bson.M{
{
"stringField": "foo1",
"intField": "123",
},
{
"stringField": "foo2",
"intField": "456",
},
}
bs, err := json.Marshal(i)
if err != nil {
panic(err)
}
var o []data
if err := json.Unmarshal(bs, &o); err != nil {
panic(err)
}
fmt.Println("Out: ", o)
}
Notice the intField,string. You can read more about this here

Resources