Fill a slice with field pointers of a struct - go

I'm creating a function which receives an interface{}, which will always be a struct, but it could be any struct.
I need to fill one slice with pointers of all the fields of the struct received. The place in the code below where I need to pick the field pointer is flagged with **FIELD POINTER**
My final goal is create a function to receive a struct equivalent to the return of the query sent in the parameter sqlQuery. I want to create a dynamic function to perform any type of query to select, always using the struct received for .Scan.
It may be that I'm thinking the wrong way, I'm still starting Golang.
func QuerySelect(entity interface{}, sqlQuery string) {
val := reflect.Indirect(reflect.ValueOf(entity))
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
"password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
// Validate database params
db, err := sql.Open(driver, psqlInfo)
// If returned any error
if err != nil {
panic(err)
}
// Validate connection with database
err = db.Ping()
if err != nil {
panic(err)
}
// Execute query
rows, err := db.Query(sqlQuery)
countColumns := val.Type().NumField()
var allRows []interface{}
for rows.Next() {
columnsPointers := make([]interface{}, countColumns)
for i := 0; i < countColumns; i++ {
columnsPointers[i] = **FIELD POINTER (entity struct)**
}
if err := rows.Scan(columnsPointers...); err != nil {
log.Fatal(err)
}
allRows = append(allRows, entity)
}
}

Per mkopriva's commentary on the question, use the following to get the address of the field:
for i := 0; i < countColumns; i++ {
columnsPointers[i] = val.Field(i).Addr().Interface()
}

Related

How to list all the items in a table with pagination

I'm trying to list all the items in a DynamoDB table with pagination, and here below is my attempt:
const tableName = "RecordingTable"
type Recording struct {
ID string `dynamodbav:"id"`
CreatedAt string `dynamodbav:"createdAt"`
UpdatedAt string `dynamodbav:"updatedAt"`
Duration int `dynamodbav:"duration"`
}
type RecordingRepository struct {
ctx context.Context
svc *dynamodb.Client
}
func NewRecordingRepository(ctx context.Context) (*RecordingRepository, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}
return &RecordingRepository{ctx, dynamodb.NewFromConfig(cfg)}, nil
}
func (r *RecordingRepository) List(page int, size int) ([]Recording, error) {
size32 := int32(size)
queryInput := &dynamodb.QueryInput{
TableName: aws.String(tableName),
Limit: &size32,
}
recordings := []Recording{}
queryPaginator := dynamodb.NewQueryPaginator(r.svc, queryInput)
for i := 0; queryPaginator.HasMorePages(); i++ {
result, err := queryPaginator.NextPage(r.ctx)
if err != nil {
return nil, err
}
if i == page {
if result.Count > 0 {
for _, v := range result.Items {
recording := Recording{}
if err := attributevalue.UnmarshalMap(v, &recording); err != nil {
return nil, err
}
recordings = append(recordings, recording)
}
}
break
}
}
return recordings, nil
}
When I run the code above, I get the following error message:
api error ValidationException: Either the KeyConditions or KeyConditionExpression parameter must be specified in the request.
But why should I specify a KeyConditionExpression when I want to get all the items? Is there another way to go or a workaround this?
Query does need your keys. It is meant to find specific items in your DynamoDB. To get all items in your DynamoDB, you need to use the Scan operation.
This should be easily fixed in your code.
Instead of QueryInput use ScanInput and instead of NewQueryPaginator use NewScanPaginator.
Just replaced QueryInput with ScanInput and QueryPaginator with ScanPaginator.

How to use rows.Scan of Go's database/sql

I use database/sql and define a struct mapping to DB table columns(tag field):
// Users ...
type Users struct {
ID int64 `field:"id"`
Username string `field:"username"`
Password string `field:"password"`
Tel string `field:"tel"`
}
then I query:
rows, err := db.Query(sql) // select * from users
if err != nil {
fmt.Println(err)
}
defer rows.Close()
for rows.Next() {
user := new(Users)
// works but I don't think it is good code for too many columns
err = rows.Scan(&user.ID, &user.Username, &user.Password, &user.Tel)
// TODO: How to scan in a simple way
if err != nil {
fmt.Println(err)
}
fmt.Println("user: ", user)
list = append(list, *user)
}
if err := rows.Err(); err != nil {
fmt.Println(err)
}
As you can see for rows.Scan() , I have to write all columns , and I don't think it's a good way for 20 or more columns .
How to scan in a clear way.
It's a good practice for using reflect:
for rows.Next() {
user := Users{}
s := reflect.ValueOf(&user).Elem()
numCols := s.NumField()
columns := make([]interface{}, numCols)
for i := 0; i < numCols; i++ {
field := s.Field(i)
columns[i] = field.Addr().Interface()
}
err := rows.Scan(columns...)
if err != nil {
log.Fatal(err)
}
log.Println(user)
}
You may consider using jmoiron's sqlx package. It has support for assigning to a struct.
Excerpt from the readme:
type Place struct {
Country string
City sql.NullString
TelCode int
}
places := []Place{}
err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
if err != nil {
fmt.Println(err)
return
}

golang reflect into []interface{}

i wanna create a mini framework which takes a simple struct and creates a full crud out of it. i already started and the "findOne, update,create,delete" is working. not i have a problem to create a findAll method. to be more clear, i dont know how to use reflect to address my ptr to an array of struct.
Here a small example for the findOne function.
type company struct {
Id int
Name string
}
comp.InitModel(newDbConnection(), &comp)
In InitModel i can fill the pointer to the company with the following:
//m.caller = pointer to the ptr to comp (struct)
callerV := reflect.ValueOf(m.caller)
CallerField := callerV.Elem()
var values []interface{}
for _, e := range m.columns {
values = append(values, CallerField.FieldByName(e.name).Addr().Interface())
}
err := r.Scan(values...)
if err != nil {
return err
}
Now i wanna create a findAll method which would be called like this
var companies []company
comp.InitModel(newDbConnection(), &comp)
comp.FindAll(&companies) //in this is the db query and scan
fmt.Println(companies) //here should be the result
But i have a problem to get the reflect with a []interface working.
func (m *Model) FindAll(test []interface{}, c *Condition) error {
//get the sql statement from the struct
stmt := PrepairStmt(m, c)
rows, err := m.db.Query(stmt.selectParse(), c.arguments...)
if err != nil {
return err
}
defer rows.Close()
callerV := reflect.ValueOf(m.caller)
CallerField := callerV.Elem()
for rows.Next() {
var values []interface{}
for _, e := range m.columns {
values = append(values, CallerField.FieldByName(e.name).Addr().Interface())
}
err = rows.Scan(values...)
if err != nil {
return err
}
valuePtr := reflect.New(reflect.TypeOf(test).Elem())
test = reflect.Append(test,reflect.ValueOf(values))
}
return nil
}
Thats were my latest tries.
maybe someone can help me with this.
i would be really thankful
Use interface{} instead of []interface{} as the argument type:
func (m *Model) FindAll(result interface{}, c *Condition) error {
stmt := PrepairStmt(m, c)
rows, err := m.db.Query(stmt.selectParse(), c.arguments...)
if err != nil {
return err
}
defer rows.Close()
// resultv is the result slice
resultv := reflect.ValueOf(result).Elem()
// rowt is the struct type
rowt := resultv.Type().Elem()
// allocate a value for the row
rowv := reflect.New(rowt).Elem()
// collect values for scan
var values []interface{}
for _, e := range m.columns {
values = append(values, rowv.FieldByName(e.name).Addr().Interface())
}
for rows.Next() {
err = rows.Scan(values...)
if err != nil {
return err
}
// Append struct to result slice. Because the struct
// is copied in append, we can reuse the struct in
// this loop.
resultv.Set(reflect.Append(resultv, rowv))
}
return nil
}
Use it like this:
var companies []company
comp.InitModel(newDbConnection(), &comp)
comp.FindAll(&companies) //in this is the db query and scan

Map response to a struct using Golang

I am attempting to map a response from an API to a struct using Golang.
The JSON that comes back when I view the link in the browser is below:
{
"GBP": 657.54
}
And I just want to map it to a simple struct like so:
type Price struct {
Name string
Value float64
}
Here is my current code.
func FetchCoinPrice(fsym string, tsyms string) Price {
url := fmt.Sprintf("https://min-api.cryptocompare.com/data/price?fsym=" + fsym + "&tsyms=" + tsyms)
fmt.Println("Requesting data from " + url)
price := Price{}
// getting the data using http
request, err := http.Get(url)
if err != nil {
log.Fatal(err.Error())
}
// Read the response body using ioutil
body, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Fatal(err.Error())
}
defer request.Body.Close()
if request.StatusCode == http.StatusOK {
json.Unmarshal(body, &price)
}
return price
}
At the moment all I receive is an empty struct, I know the link is bringing back the correct data and I've tested it in my browser.
The mapping doesn't work that way. Instead, you should use a map:
data := []byte(`{
"GBP": 657.54
}`)
priceMap := map[string]float64{}
err := json.Unmarshal(data, &priceMap)
// Check your errors!
if err != nil {
log.Fatal(err.Error())
}
fmt.Println(priceMap)
This will print:
map[GBP:657.54]
You can then iterate over the map and build the struct you mentioned above, or just access the entry directly if you know the currency. eg: priceMap["GBP"]
You should really check your errors, especially if you're not getting the output you expect from Unmarshal.
The problem is that the unmarshaler cannot guess that keys in a JSON object should correspond to some value in a struct. Golang JSON mapping simply doesn't work that way.
However, you can make your "Price" type implement json.Unmarshaler to deserialize a message into a map of floats (map[string]float64) then ensure the shape is right and populate the struct accordingly:
func (p *Price) UnmarshalJSON(bs []byte) error {
kvs := map[string]float64{}
err := json.Unmarshal(bs, &kvs)
if err != nil {
return err
}
if len(kvs) != 1 {
return fmt.Errorf("expected 1 key, got %d", len(kvs))
}
for name, value := range kvs {
p.Name, p.Value = name, value
}
return nil
}
func main() {
jsonstr := `[{"GBP":657.54},{"USD":123.45}]`
ps := []Price{}
err := json.Unmarshal([]byte(jsonstr), &ps)
if err != nil {
panic(err)
}
// ps=[]main.Price{
// main.Price{Name:"GBP", Value:657.54},
// main.Price{Name:"USD", Value:123.45}
// }
}

How to append objects to a slice?

I am new to golang and I'd like to aggregaet query results into a results slice to be pushed to the browser. Here is the code:
type Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
Tasks []Task
}
type Cats struct {
category Category
}
func CategoriesCtrl(w http.ResponseWriter, req *http.Request) {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
c := session.DB("taskdb").C("categories")
iter := c.Find(nil).Iter()
result := Category{}
results := []Cats //Here is the problem
for iter.Next(&result) {
results = append(results, result)
fmt.Printf("Category:%s, Description:%s\n", result.Name, result.Description)
tasks := result.Tasks
for _, v := range tasks {
fmt.Printf("Task:%s Due:%v\n", v.Description, v.Due)
}
}
if err = iter.Close(); err != nil {
log.Fatal(err)
}
fmt.Fprint(w, results)
}
But instead I get
type []Cats is not an expression
How can I fix this?
You can say
results := make([]Cats, 0)
or
var results []Cats
or
results := []Cats{}
instead.
You can use results := make([]Cats, len) instead, where len is the initial length of slice.
results := []Cats{} will also work.
If you use var results []Cats, its initial value is nil so you'd need to initialize it before using append.

Resources