Compare commits
18 Commits
main
...
new/insert
| Author | SHA1 | Date | |
|---|---|---|---|
| 77b8bdf218 | |||
| 2161c201bc | |||
| 935285a9f3 | |||
| 5f24529730 | |||
| 5563e93efd | |||
| d0ec9465e2 | |||
| 0c4a1abb9a | |||
| 791ffad0e3 | |||
| 2415af38b2 | |||
| 053f5b5fd8 | |||
| 6326711424 | |||
| 6a9cdafcf1 | |||
| 5e2ddecdcc | |||
| 9033530a55 | |||
| 6bbb452b03 | |||
| 21b1291dbc | |||
| c62da47a31 | |||
| ff62b371c9 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -15,7 +15,15 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
cmd/*
|
||||
#ignore test files
|
||||
cmd/
|
||||
@@ -13,7 +13,7 @@ Lazy is a helper tool when working with [sqlx](https://github.com/jmoiron/sqlx)
|
||||
<hr />
|
||||
|
||||
## RandomGenerate
|
||||
Generates random data for basic types and slices to be used with [sqlmock](https://github.com/DATA-DOG/go-sqlmock). Slices are given an arbitrary amount entries See [Mocks](#mocks) for more information. Lazy supports many bind types using sqlx's Rebind method, however, due to this conversion queries that use operators like postgres' jsonb "?" operator cannot be paired with the BindQuestion option as sqlx will greedily replace all "?" causing syntax errors
|
||||
Generates random data for basic types and slices to be used with [sqlmock](https://github.com/DATA-DOG/go-sqlmock). Slices are given an arbitrary amount entries See [Mocks](#mocks) for more information.
|
||||
```go
|
||||
func RandomGenerate(m Config) (*Mock, error)
|
||||
```
|
||||
@@ -73,8 +73,7 @@ func ExampleTest() {
|
||||
Query: "SELECT key, count FROM table WHERE key IN (?, ?)",
|
||||
Example: SqlResultsExample{},
|
||||
Keys: []any{ 1, 6 },
|
||||
RowCount: 2,
|
||||
BindType: lazy.BindQuestion,
|
||||
RowCount: 2,
|
||||
}
|
||||
mock, err := lazy.GenerateRandom(cfg)
|
||||
if err != nil {
|
||||
|
||||
205
lazy.go
205
lazy.go
@@ -1,14 +1,12 @@
|
||||
package lazy
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
@@ -17,61 +15,71 @@ const (
|
||||
DB_TAG = "db" //tag used to parse sql fields in example struct
|
||||
LAZY_TAG = "lazy" //tag label for KEY_VALUE
|
||||
KEY_VALUE = "key" //tag value for LAZY_TAG
|
||||
//bind values
|
||||
BindQuestion Binds = 1
|
||||
BindDollar Binds = 2
|
||||
BindNamed Binds = 3
|
||||
BindAt Binds = 4
|
||||
)
|
||||
|
||||
type Binds int
|
||||
var (
|
||||
ErrBadExample = errors.New("example field must be a non-nil struct")
|
||||
ErrMissingKeys = errors.New("there must be a key for each row requested with RowCount")
|
||||
//contains placeholder to inject type on runtime
|
||||
ErrUnsupportedType = errors.New("type: %s is not yet supported")
|
||||
)
|
||||
|
||||
// mock data generated based on config
|
||||
type Mock struct {
|
||||
type RowMock struct {
|
||||
Query string
|
||||
Columns []string
|
||||
Rows [][]driver.Value
|
||||
}
|
||||
|
||||
// configuration for RandomGenerate
|
||||
type Config struct {
|
||||
type RowConfig struct {
|
||||
Query string
|
||||
Example any
|
||||
Keys []any //length of keys must = RowCount, if set
|
||||
Keys []any
|
||||
RowCount int
|
||||
BindType Binds
|
||||
}
|
||||
|
||||
type StructConfig struct {
|
||||
Query string
|
||||
Example any
|
||||
RowCount int
|
||||
}
|
||||
|
||||
type StructMock struct {
|
||||
Query string
|
||||
MockStructs []any
|
||||
}
|
||||
|
||||
// generates mock data based on configuration to be used for sqlmock
|
||||
func RandomGenerate(m Config) (*Mock, error) {
|
||||
func RandomRows(r RowConfig) (*RowMock, error) {
|
||||
//example struct cannot be nil and must be a struct
|
||||
if m.Example == nil {
|
||||
return nil, errors.New("example value cannot be nil")
|
||||
if r.Example == nil {
|
||||
return nil, ErrBadExample
|
||||
}
|
||||
if reflect.ValueOf(m.Example).Kind() != reflect.Struct {
|
||||
return nil, errors.New("example value must be a struct")
|
||||
if reflect.ValueOf(r.Example).Kind() != reflect.Struct {
|
||||
return nil, ErrBadExample
|
||||
}
|
||||
|
||||
//any weirdness, just pull one row
|
||||
if m.RowCount <= 0 {
|
||||
m.RowCount = 1
|
||||
if r.RowCount <= 0 {
|
||||
r.RowCount = 1
|
||||
}
|
||||
|
||||
//if keys are set, ensure there are enough to populate requested rows
|
||||
primaryKey := false
|
||||
if len(m.Keys) > 0 {
|
||||
if len(r.Keys) > 0 {
|
||||
primaryKey = true
|
||||
if len(m.Keys) != m.RowCount {
|
||||
return nil, errors.New("you must provide a key for each row")
|
||||
if len(r.Keys) != r.RowCount {
|
||||
return nil, ErrMissingKeys
|
||||
}
|
||||
}
|
||||
|
||||
retType := reflect.TypeOf(m.Example)
|
||||
retType := reflect.TypeOf(r.Example)
|
||||
maxFieldCount := retType.NumField()
|
||||
columns := make([]string, 0, maxFieldCount)
|
||||
rows := make([][]driver.Value, 0)
|
||||
|
||||
for y := 0; y < m.RowCount; y++ {
|
||||
for y := 0; y < r.RowCount; y++ {
|
||||
rows = append(rows, make([]driver.Value, 0))
|
||||
for x := 0; x < maxFieldCount; x++ {
|
||||
field := retType.Field(x)
|
||||
@@ -87,94 +95,86 @@ func RandomGenerate(m Config) (*Mock, error) {
|
||||
|
||||
//if field has lazy:"key" tag and tags are present inject primary key
|
||||
if field.Tag.Get(LAZY_TAG) == KEY_VALUE && primaryKey {
|
||||
rows[y] = append(rows[y], m.Keys[y])
|
||||
rows[y] = append(rows[y], r.Keys[y])
|
||||
continue
|
||||
}
|
||||
|
||||
//generate random values
|
||||
nv := kindToRandom(field)
|
||||
if nv == nil {
|
||||
return nil, fmt.Errorf("could not match type: %s", retType.Name())
|
||||
return nil, fmt.Errorf(ErrUnsupportedType.Error(), field.Type.Name())
|
||||
}
|
||||
|
||||
rows[y] = append(rows[y], nv)
|
||||
}
|
||||
}
|
||||
|
||||
return &Mock{
|
||||
return &RowMock{
|
||||
//sql is rebound and escaped for sqlmock.ExpectQuery
|
||||
Query: sqlx.Rebind(int(m.BindType), regexp.QuoteMeta(m.Query)),
|
||||
Query: sqlx.Rebind(sqlx.AT, regexp.QuoteMeta(r.Query)),
|
||||
Columns: columns,
|
||||
Rows: rows,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// converts basic reflect Kind's to psuedo-random data, slices are given a random amount of entries
|
||||
func kindToRandom(field reflect.StructField) any {
|
||||
//this isn't ideal, but since the sql types are structs they were being filled in a weird
|
||||
//manner. For now we check those types first, and will clean this up later.
|
||||
switch field.Type {
|
||||
case reflect.TypeOf(sql.NullTime{}):
|
||||
var temp sql.NullTime
|
||||
if !isNull() {
|
||||
temp.Valid = true
|
||||
temp.Time = time.Now()
|
||||
}
|
||||
return temp
|
||||
case reflect.TypeOf(sql.NullInt16{}):
|
||||
var temp sql.NullInt16
|
||||
if isNull() {
|
||||
temp.Valid = false
|
||||
} else {
|
||||
temp.Valid = true
|
||||
temp.Int16 = int16(rand.Int())
|
||||
}
|
||||
return temp
|
||||
case reflect.TypeOf(sql.NullInt32{}):
|
||||
var temp sql.NullInt32
|
||||
if isNull() {
|
||||
temp.Valid = false
|
||||
} else {
|
||||
temp.Valid = true
|
||||
temp.Int32 = rand.Int32()
|
||||
}
|
||||
return temp
|
||||
case reflect.TypeOf(sql.NullInt64{}):
|
||||
var temp sql.NullInt64
|
||||
if isNull() {
|
||||
temp.Valid = false
|
||||
} else {
|
||||
temp.Valid = true
|
||||
temp.Int64 = rand.Int64()
|
||||
}
|
||||
return temp
|
||||
case reflect.TypeOf(sql.NullBool{}):
|
||||
var temp sql.NullBool
|
||||
if isNull() {
|
||||
temp.Valid = false
|
||||
} else {
|
||||
temp.Valid = true
|
||||
temp.Bool = (rand.Int()%2 == 0)
|
||||
}
|
||||
return temp
|
||||
case reflect.TypeOf(sql.NullFloat64{}):
|
||||
var temp sql.NullFloat64
|
||||
if isNull() {
|
||||
temp.Valid = false
|
||||
} else {
|
||||
temp.Valid = true
|
||||
temp.Float64 = rand.Float64()
|
||||
}
|
||||
return temp
|
||||
case reflect.TypeOf(sql.NullString{}):
|
||||
var temp sql.NullString
|
||||
if isNull() {
|
||||
temp.Valid = false
|
||||
} else {
|
||||
temp.Valid = true
|
||||
temp.String = fmt.Sprintf("random %d", rand.Int())
|
||||
}
|
||||
return temp
|
||||
// generates mock data for insert statements to be used for sqlmock, it also returns a slice of
|
||||
// example structs with mock data
|
||||
func RandomStruct(c StructConfig) (*StructMock, error) {
|
||||
//make sure we have an example struct
|
||||
if c.Example == nil {
|
||||
return nil, ErrBadExample
|
||||
}
|
||||
if reflect.ValueOf(c.Example).Kind() != reflect.Struct {
|
||||
return nil, ErrBadExample
|
||||
}
|
||||
|
||||
//we need at least one row
|
||||
if c.RowCount <= 0 {
|
||||
c.RowCount = 1
|
||||
}
|
||||
|
||||
//get example type and number of fields
|
||||
retType := reflect.TypeOf(c.Example)
|
||||
maxFieldCount := retType.NumField()
|
||||
|
||||
//create slice of example structs
|
||||
ft := reflect.SliceOf(retType)
|
||||
filled := reflect.MakeSlice(ft, 0, c.RowCount)
|
||||
|
||||
for x := 0; x < c.RowCount; x++ {
|
||||
//add empty example struct
|
||||
filled = reflect.Append(filled, reflect.ValueOf(c.Example))
|
||||
for y := 0; y < maxFieldCount; y++ {
|
||||
field := retType.Field(y)
|
||||
|
||||
//get random value
|
||||
nv := kindToRandom(field)
|
||||
if nv == nil {
|
||||
return nil, fmt.Errorf(ErrUnsupportedType.Error(), field.Type.Name())
|
||||
}
|
||||
|
||||
if filled.Index(x).Field(y).CanSet() {
|
||||
filled.Index(x).Field(y).Set(reflect.ValueOf(nv))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//convert reflect.Value to []any
|
||||
fl := filled.Len()
|
||||
retStructs := make([]any, fl)
|
||||
for x := 0; x < fl; x++ {
|
||||
retStructs[x] = filled.Index(x).Interface()
|
||||
}
|
||||
|
||||
return &StructMock{
|
||||
Query: sqlx.Rebind(sqlx.AT, regexp.QuoteMeta(c.Query)),
|
||||
MockStructs: retStructs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// converts basic reflect Kind's to psuedo-random data, it is super basic and supports very basic types. Slices and arrays
|
||||
// are supported, slices will get 1-10 entries and arrays will fill length
|
||||
func kindToRandom(field reflect.StructField) any {
|
||||
kind := field.Type.Kind()
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
@@ -188,11 +188,9 @@ func kindToRandom(field reflect.StructField) any {
|
||||
case reflect.Float64:
|
||||
return rand.Float64()
|
||||
case reflect.String:
|
||||
return fmt.Sprintf("random %d", rand.Int())
|
||||
return fmt.Sprintf("%d", rand.Int())
|
||||
case reflect.Bool:
|
||||
return rand.Int()%2 == 0
|
||||
case reflect.TypeOf(time.Time{}).Kind():
|
||||
return time.Now()
|
||||
case reflect.Array:
|
||||
underlying := field.Type.Elem().Kind()
|
||||
count := reflect.ValueOf(field).Len() //fill entire length
|
||||
@@ -277,7 +275,7 @@ func kindToRandom(field reflect.StructField) any {
|
||||
case reflect.String:
|
||||
var strslice []string
|
||||
for x := 0; x < count; x++ {
|
||||
strslice = append(strslice, fmt.Sprintf("random %d", rand.Int()))
|
||||
strslice = append(strslice, fmt.Sprintf("%d", rand.Int()))
|
||||
}
|
||||
return strslice
|
||||
case reflect.Bool:
|
||||
@@ -291,14 +289,3 @@ func kindToRandom(field reflect.StructField) any {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// a quick and dirty rng function to determine if nulltypes are null or
|
||||
// if we will generate a random value
|
||||
func isNull() bool {
|
||||
x := rand.IntN(2)
|
||||
if x%2 == 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user