Skip to content

Commit

Permalink
Merge pull request #3 from rocketlaunchr/new-features
Browse files Browse the repository at this point in the history
New features
  • Loading branch information
rocketlaunchr-cto authored Aug 29, 2019
2 parents 1c4c3c7 + 1810d18 commit 7b872d3
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 92 deletions.
72 changes: 34 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
dbq - Barbeque the boilerplate code [![GoDoc](http://godoc.org/github.com/rocketlaunchr/dbq?status.svg)](http://godoc.org/github.com/rocketlaunchr/dbq) [![Go Report Card](https://goreportcard.com/badge/github.com/rocketlaunchr/dbq)](https://goreportcard.com/report/github.com/rocketlaunchr/dbq)
===============
# dbq - Barbeque the boilerplate code [![GoDoc](http://godoc.org/github.com/rocketlaunchr/dbq?status.svg)](http://godoc.org/github.com/rocketlaunchr/dbq) [![Go Report Card](https://goreportcard.com/badge/github.com/rocketlaunchr/dbq)](https://goreportcard.com/report/github.com/rocketlaunchr/dbq)

<p align="center">
<img src="https://github.com/rocketlaunchr/dbq/raw/master/logo.png" alt="dbq" />
Expand All @@ -9,57 +8,53 @@ dbq - Barbeque the boilerplate code [![GoDoc](http://godoc.org/github.com/rocket

Everyone knows that performing simple **DATABASE queries** in Go takes numerous lines of code that is often repetitive. If you want to avoid the cruft, you have two options: A heavy-duty ORM that is not up to the standard of Laraval or Django. Or DBQ!


**WARNING: You will seriously reduce your database code to a few lines**


## What is included

* Supports ANY type of query
* **MySQL** and **PostgreSQL** compatible
* **Convenient** and **Developer Friendly**
* Bulk Insert seamlessly
* Automatically unmarshal query results directly to a struct using [mapstructure](https://github.com/mitchellh/mapstructure) package
* Lightweight
* Compatible with [mysql-go](https://github.com/rocketlaunchr/mysql-go) for proper MySQL query cancelation
- Supports ANY type of query
- **MySQL** and **PostgreSQL** compatible
- **Convenient** and **Developer Friendly**
- Accepts any type of slice for query args
- Flattens query arg slices to individual values
- Bulk Insert seamlessly
- Automatically unmarshal query results directly to a struct using [mapstructure](https://github.com/mitchellh/mapstructure) package
- Lightweight
- Compatible with [mysql-go](https://github.com/rocketlaunchr/mysql-go) for proper MySQL query cancelation

## Dependencies

* [MySQL driver](https://github.com/go-sql-driver/mysql) OR
* [PostgreSQL driver](https://github.com/lib/pq)

- [MySQL driver](https://github.com/go-sql-driver/mysql) OR
- [PostgreSQL driver](https://github.com/lib/pq)

## Installation

```
go get -u github.com/rocketlaunchr/dbq
```


## Examples

Let's assume a table called `users`:

| id | name | age | created_at |
|----|-------|-----|------------|
| 1 | Sally | 12 | 2019-03-01 |
| 2 | Peter | 15 | 2019-02-01 |
| 3 | Tom | 18 | 2019-01-01 |

| id | name | age | created_at |
| --- | ----- | --- | ---------- |
| 1 | Sally | 12 | 2019-03-01 |
| 2 | Peter | 15 | 2019-02-01 |
| 3 | Tom | 18 | 2019-01-01 |

### Bulk Insert

You can insert multiple rows at once.


```go

db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/db")

users := []interface{}{
[]interface{}{"Brad", 45, time.Now()},
[]interface{}{"Ange", 36, time.Now()},
[]interface{}{"Emily", 22, time.Now()},
[]interface{}{"Brad", 45, time.Now()},
[]interface{}{"Ange", 36, time.Now()},
[]interface{}{"Emily", 22, time.Now()},
}

stmt := dbq.INSERT("users", []string{"name", "age", "created_at"}, len(users))
Expand All @@ -76,10 +71,10 @@ unmarshal to a struct. You will need to type assert the results.
```go

type user struct {
ID int `dbq:"id"`
Name string `dbq:"name"`
Age int `dbq:"age"`
CreatedAt time.Time `dbq:"created_at"`
ID int `dbq:"id"`
Name string `dbq:"name"`
Age int `dbq:"age"`
CreatedAt time.Time `dbq:"created_at"`
}

opts := &dbq.Options{ConcreteStruct: user{}, DecoderConfig:x}
Expand Down Expand Up @@ -138,9 +133,9 @@ If you know that the query will return at maximum 1 row:
```go
result := dbq.MustQ(ctx, db, "SELECT * FROM users LIMIT 1", dbq.SingleResult)
if result == nil {
// no result
// no result
} else {
result.(map[string]interface{})
result.(map[string]interface{})
}

```
Expand All @@ -166,18 +161,19 @@ defer conn.Close()

result := dbq.MustQ(ctx, conn, "SELECT * FROM users LIMIT 1", dbq.SingleResult)
if result == nil {
// no result
// no result
} else {
result.(map[string]interface{})
result.(map[string]interface{})
}
```

## Other useful packages
* [remember-go](https://github.com/rocketlaunchr/remember-go) - Cache slow database queries
* [mysql-go](https://github.com/rocketlaunchr/mysql-go) - Properly cancel slow MySQL queries
* [react](https://github.com/rocketlaunchr/react) - Build front end applications using Go
* [igo](https://github.com/rocketlaunchr/igo) - A Go transpiler with cool new syntax such as fordefer (defer for for-loops)
* [dataframe-go](https://github.com/rocketlaunchr/dataframe-go) - Statistics and data manipulation.

- [remember-go](https://github.com/rocketlaunchr/remember-go) - Cache slow database queries
- [mysql-go](https://github.com/rocketlaunchr/mysql-go) - Properly cancel slow MySQL queries
- [react](https://github.com/rocketlaunchr/react) - Build front end applications using Go
- [igo](https://github.com/rocketlaunchr/igo) - A Go transpiler with cool new syntax such as fordefer (defer for for-loops)
- [dataframe-go](https://github.com/rocketlaunchr/dataframe-go) - Statistics and data manipulation.

#

Expand Down
60 changes: 56 additions & 4 deletions dbq.igo
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
stdSql "database/sql"
"encoding/json"
"golang.org/x/xerrors"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -49,6 +50,15 @@ type StructorConfig struct {
WeaklyTypedInput bool
}

// PostUnmarshaler allows you to further modify all results after unmarshaling.
// The ConcreteStruct pointer must implement this interface to make use of this feature.
type PostUnmarshaler interface {

// PostUnmarshal is called for each row after all results have been fetched.
// You can use it to further modify the values of each ConcreteStruct.
PostUnmarshal(row, count int) error
}

// SingleResult is a convenient option for the common case of expecting
// a single result from a query.
var SingleResult = &Options{SingleResult: true}
Expand All @@ -75,6 +85,10 @@ type Options struct {
// single result directly (instead of wrapped in a slice). This makes it easier to
// type assert.
SingleResult bool

// PostFetch is called after all results are fetched but before PostUnmarshaler is called (if applicable).
// It can be used to return a database connection back to the pool.
PostFetch func()
}

// MustE is a wrapper around the E function. It will panic upon encountering an error.
Expand Down Expand Up @@ -140,11 +154,25 @@ func Q(ctx context.Context, db interface{}, query string, options *Options, args
query = strings.TrimSpace(query)
queryType := query[0:6]

if len(args) == 1 {
// Convert slice to []interface{}
if arg := reflect.ValueOf(args[0]); arg.Kind() == reflect.Slice {
args = sliceConv(arg)
// Check if any arguments are slices
foundSliceArg := false
for _, v := range args {
if arg := reflect.ValueOf(v); arg.Kind() == reflect.Slice {
foundSliceArg = true
break
}
}

if foundSliceArg {
newArgs := []interface{}{}
for _, v := range args {
if arg := reflect.ValueOf(v); arg.Kind() == reflect.Slice {
newArgs = append(newArgs, sliceConv(arg)...)
} else {
newArgs = append(newArgs, v)
}
}
args = newArgs
}

if queryType == "INSERT" || queryType == "insert" {
Expand Down Expand Up @@ -540,6 +568,30 @@ func Q(ctx context.Context, db interface{}, query string, options *Options, args
return nil, err
}

// Call PostFetch
if o.PostFetch != nil {
o.PostFetch()
}

// Call PostUnmarshaler
if o.ConcreteStruct != nil && len(out) > 0 {
csTyp := reflect.TypeOf(reflect.New(reflect.TypeOf(o.ConcreteStruct)).Interface())
ics := reflect.TypeOf((*PostUnmarshaler)(nil)).Elem()

if csTyp.Implements(ics) {
rows := reflect.ValueOf(out)
count := rows.Len()
for i := 0; i < count; i++ {
row := reflect.ValueOf(rows.Index(i).Interface())
retVals := row.MethodByName("PostUnmarshal").Call([]reflect.Value{reflect.ValueOf(i), reflect.ValueOf(count)})
err := retVals[0].Interface()
if err != nil {
return nil, xerrors.Errorf("dbq.PostUnmarshal @ row %d: %w", i, err)
}
}
}
}

return out, nil
}
}
Loading

0 comments on commit 7b872d3

Please sign in to comment.