Skip to content

Commit

Permalink
#102: Change Chunk method to accept struct and returning slice of tho…
Browse files Browse the repository at this point in the history
…se structs (#103)

* #102: Change Chunk method to accept struct and returning slice of those structs
Add common abstract method with EachToStruct processing data
Add more tests to cover errs
  • Loading branch information
arthurkushman authored Oct 16, 2023
1 parent 52d5874 commit c257705
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 68 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,13 +433,16 @@ If you need to work with thousands of database records, consider using the chunk
This method retrieves a small chunk of the results at a time and feeds each chunk into a closure for processing.

```go
err = db.Table("user_achievements").Select("points").Where("id", "=", id).Chunk(100, func (users []map[string]interface{}) bool {
for _, m := range users {
if val, ok := m["points"];ok {
pointsCalc += diffFormula(val.(int64))
}
// or you can return false here to stop running chunks
var sumOfPoints int64
dataStruct := &DataStructUser{}
err = db.Table(UsersTable).Select("name", "points").Chunk(dataStruct, 100, func(users []any) bool {
for _, v := range users {
user := v.(DataStructUser)
// your code goes here e.g.:
sumOfPoints += user.Points
}

// or you can return false here to stop running chunks
return true
})
```
Expand Down
50 changes: 40 additions & 10 deletions advanced.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package buildsqlx

import (
"database/sql"
"fmt"
"math"
"reflect"
"strconv"
)

Expand Down Expand Up @@ -42,6 +44,7 @@ func (r *DB) Pluck(column string) (val []interface{}, err error) {
for k, m := range res {
val[k] = m[column]
}

return
}

Expand All @@ -57,18 +60,20 @@ func (r *DB) PluckMap(colKey, colValue string) (val []map[interface{}]interface{
val[k] = make(map[interface{}]interface{})
val[k][m[colKey]] = m[colValue]
}

return
}

// Exists checks whether conditional rows are existing (returns true) or not (returns false)
func (r *DB) Exists() (exists bool, err error) {
builder := r.Builder
if builder.table == "" {
bldr := r.Builder
if bldr.table == "" {
return false, errTableCallBeforeOp
}

query := `SELECT EXISTS(SELECT 1 FROM "` + builder.table + `" ` + builder.buildClauses() + `)`
query := `SELECT EXISTS(SELECT 1 FROM "` + bldr.table + `" ` + bldr.buildClauses() + `)`
err = r.Sql().QueryRow(query, prepareValues(r.Builder.whereBindings)...).Scan(&exists)

return
}

Expand All @@ -78,6 +83,7 @@ func (r *DB) DoesntExists() (bool, error) {
if err != nil {
return false, err
}

return !ex, nil
}

Expand Down Expand Up @@ -110,7 +116,7 @@ func (r *DB) incrDecr(column, sign string, on uint64) (int64, error) {

// Chunk run queries by chinks by passing user-land function with an ability to stop execution when needed
// by returning false and proceed to execute queries when return true
func (r *DB) Chunk(amount int64, fn func(rows []map[string]interface{}) bool) error {
func (r *DB) Chunk(src any, amount int64, fn func(rows []any) bool) error {
cols := r.Builder.columns
cnt, err := r.Count()
if err != nil {
Expand All @@ -123,26 +129,50 @@ func (r *DB) Chunk(amount int64, fn func(rows []map[string]interface{}) bool) er
}

if cnt < amount {
res, err := r.Get()
structRows, err := r.eachToStructRows(src, 0, 0)
if err != nil {
return err
}
fn(res) // execute all resulting records

fn(structRows) // execute all resulting records

return nil
}

// executing chunks amount < cnt
c := int64(math.Ceil(float64(cnt / amount)))
var i int64
for i = 0; i < c; i++ {
rows, err := r.Offset(i * amount).Limit(amount).Get() // by 100 rows from 100 x n
for i := int64(0); i < c; i++ {
structRows, err := r.eachToStructRows(src, i*amount, amount)
if err != nil {
return err
}
res := fn(rows)

res := fn(structRows)
if !res { // stop an execution when false returned by user
break
}
}

return nil
}

func (r *DB) eachToStructRows(src any, offset, limit int64) ([]any, error) {
var structRows []any
if limit > 0 {
r.Offset(offset).Limit(limit)
}

err := r.EachToStruct(func(rows *sql.Rows) error {
err := r.Next(rows, src)
if err != nil {
return err
}

v := reflect.ValueOf(src).Elem().Interface()
structRows = append(structRows, v)

return nil
})

return structRows, err
}
9 changes: 7 additions & 2 deletions aggregates.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ func (r *DB) Count() (cnt int64, err error) {
builder.columns = []string{"COUNT(*)"}
query := builder.buildSelect()
err = r.Sql().QueryRow(query, prepareValues(r.Builder.whereBindings)...).Scan(&cnt)

return
}

Expand All @@ -15,6 +16,7 @@ func (r *DB) Avg(column string) (avg float64, err error) {
builder.columns = []string{"AVG(" + column + ")"}
query := builder.buildSelect()
err = r.Sql().QueryRow(query, prepareValues(r.Builder.whereBindings)...).Scan(&avg)

return
}

Expand All @@ -24,6 +26,7 @@ func (r *DB) Min(column string) (min float64, err error) {
builder.columns = []string{"MIN(" + column + ")"}
query := builder.buildSelect()
err = r.Sql().QueryRow(query, prepareValues(r.Builder.whereBindings)...).Scan(&min)

return
}

Expand All @@ -33,14 +36,16 @@ func (r *DB) Max(column string) (max float64, err error) {
builder.columns = []string{"MAX(" + column + ")"}
query := builder.buildSelect()
err = r.Sql().QueryRow(query, prepareValues(r.Builder.whereBindings)...).Scan(&max)

return
}

// Sum calculates sum for specified column
func (r *DB) Sum(column string) (max float64, err error) {
func (r *DB) Sum(column string) (sum float64, err error) {
builder := r.Builder
builder.columns = []string{"SUM(" + column + ")"}
query := builder.buildSelect()
err = r.Sql().QueryRow(query, prepareValues(r.Builder.whereBindings)...).Scan(&max)
err = r.Sql().QueryRow(query, prepareValues(r.Builder.whereBindings)...).Scan(&sum)

return
}
40 changes: 13 additions & 27 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,10 @@ func (r *DB) Rename(from, to string) (sql.Result, error) {
// WhereIn appends IN (val1, val2, val3...) stmt to WHERE clause
func (r *DB) WhereIn(field string, in any) *DB {
ins, err := interfaceToSlice(in)
if err != nil {
return nil
if err != nil { // don't want the code run on prod falling just because user didn't pass slice as `in` param
log.Panicln(err)
}

r.buildWhere("", field, "IN", ins)
return r
}
Expand All @@ -382,8 +383,9 @@ func (r *DB) WhereIn(field string, in any) *DB {
func (r *DB) WhereNotIn(field string, in any) *DB {
ins, err := interfaceToSlice(in)
if err != nil {
return nil
log.Panicln(err)
}

r.buildWhere("", field, "NOT IN", ins)
return r
}
Expand All @@ -392,8 +394,9 @@ func (r *DB) WhereNotIn(field string, in any) *DB {
func (r *DB) OrWhereIn(field string, in any) *DB {
ins, err := interfaceToSlice(in)
if err != nil {
return nil
log.Panicln(err)
}

r.buildWhere("OR", field, "IN", ins)
return r
}
Expand All @@ -402,8 +405,9 @@ func (r *DB) OrWhereIn(field string, in any) *DB {
func (r *DB) OrWhereNotIn(field string, in any) *DB {
ins, err := interfaceToSlice(in)
if err != nil {
return nil
log.Panicln(err)
}

r.buildWhere("OR", field, "NOT IN", ins)
return r
}
Expand All @@ -412,8 +416,9 @@ func (r *DB) OrWhereNotIn(field string, in any) *DB {
func (r *DB) AndWhereIn(field string, in any) *DB {
ins, err := interfaceToSlice(in)
if err != nil {
return nil
log.Panicln(err)
}

r.buildWhere("AND", field, "IN", ins)
// r.buildWhere("AND", field, "IN", prepareSlice(ins))
return r
Expand All @@ -423,8 +428,9 @@ func (r *DB) AndWhereIn(field string, in any) *DB {
func (r *DB) AndWhereNotIn(field string, in any) *DB {
ins, err := interfaceToSlice(in)
if err != nil {
return nil
log.Panicln(err)
}

r.buildWhere("AND", field, "NOT IN", ins)
return r
}
Expand Down Expand Up @@ -459,26 +465,6 @@ func (r *DB) AndWhereNotNull(field string) *DB {
return r.buildWhere(sqlOperatorAnd, field, sqlOperatorIs, sqlSpecificValueNotNull)
}

// prepares slice for Where bindings, IN/NOT IN etc
func prepareSlice(in []any) (out []string) {
for _, value := range in {
switch v := value.(type) {
case string:
out = append(out, v)
case int:
out = append(out, strconv.FormatInt(int64(v), 10))
case float64:
out = append(out, fmt.Sprintf("%g", v))
case int64:
out = append(out, strconv.FormatInt(v, 10))
case uint64:
out = append(out, strconv.FormatUint(v, 10))
}
}

return
}

// From prepares sql stmt to set data from another table, ex.:
// UPDATE employees SET sales_count = sales_count + 1 FROM accounts
func (r *DB) From(fromTbl string) *DB {
Expand Down
Loading

0 comments on commit c257705

Please sign in to comment.