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
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.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 />
|
<hr />
|
||||||
|
|
||||||
## RandomGenerate
|
## 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
|
```go
|
||||||
func RandomGenerate(m Config) (*Mock, error)
|
func RandomGenerate(m Config) (*Mock, error)
|
||||||
```
|
```
|
||||||
@@ -74,7 +74,6 @@ func ExampleTest() {
|
|||||||
Example: SqlResultsExample{},
|
Example: SqlResultsExample{},
|
||||||
Keys: []any{ 1, 6 },
|
Keys: []any{ 1, 6 },
|
||||||
RowCount: 2,
|
RowCount: 2,
|
||||||
BindType: lazy.BindQuestion,
|
|
||||||
}
|
}
|
||||||
mock, err := lazy.GenerateRandom(cfg)
|
mock, err := lazy.GenerateRandom(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
205
lazy.go
205
lazy.go
@@ -1,14 +1,12 @@
|
|||||||
package lazy
|
package lazy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
@@ -17,61 +15,71 @@ const (
|
|||||||
DB_TAG = "db" //tag used to parse sql fields in example struct
|
DB_TAG = "db" //tag used to parse sql fields in example struct
|
||||||
LAZY_TAG = "lazy" //tag label for KEY_VALUE
|
LAZY_TAG = "lazy" //tag label for KEY_VALUE
|
||||||
KEY_VALUE = "key" //tag value for LAZY_TAG
|
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
|
// mock data generated based on config
|
||||||
type Mock struct {
|
type RowMock struct {
|
||||||
Query string
|
Query string
|
||||||
Columns []string
|
Columns []string
|
||||||
Rows [][]driver.Value
|
Rows [][]driver.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// configuration for RandomGenerate
|
// configuration for RandomGenerate
|
||||||
type Config struct {
|
type RowConfig struct {
|
||||||
Query string
|
Query string
|
||||||
Example any
|
Example any
|
||||||
Keys []any //length of keys must = RowCount, if set
|
Keys []any
|
||||||
RowCount int
|
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
|
// 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
|
//example struct cannot be nil and must be a struct
|
||||||
if m.Example == nil {
|
if r.Example == nil {
|
||||||
return nil, errors.New("example value cannot be nil")
|
return nil, ErrBadExample
|
||||||
}
|
}
|
||||||
if reflect.ValueOf(m.Example).Kind() != reflect.Struct {
|
if reflect.ValueOf(r.Example).Kind() != reflect.Struct {
|
||||||
return nil, errors.New("example value must be a struct")
|
return nil, ErrBadExample
|
||||||
}
|
}
|
||||||
|
|
||||||
//any weirdness, just pull one row
|
//any weirdness, just pull one row
|
||||||
if m.RowCount <= 0 {
|
if r.RowCount <= 0 {
|
||||||
m.RowCount = 1
|
r.RowCount = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
//if keys are set, ensure there are enough to populate requested rows
|
//if keys are set, ensure there are enough to populate requested rows
|
||||||
primaryKey := false
|
primaryKey := false
|
||||||
if len(m.Keys) > 0 {
|
if len(r.Keys) > 0 {
|
||||||
primaryKey = true
|
primaryKey = true
|
||||||
if len(m.Keys) != m.RowCount {
|
if len(r.Keys) != r.RowCount {
|
||||||
return nil, errors.New("you must provide a key for each row")
|
return nil, ErrMissingKeys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
retType := reflect.TypeOf(m.Example)
|
retType := reflect.TypeOf(r.Example)
|
||||||
maxFieldCount := retType.NumField()
|
maxFieldCount := retType.NumField()
|
||||||
columns := make([]string, 0, maxFieldCount)
|
columns := make([]string, 0, maxFieldCount)
|
||||||
rows := make([][]driver.Value, 0)
|
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))
|
rows = append(rows, make([]driver.Value, 0))
|
||||||
for x := 0; x < maxFieldCount; x++ {
|
for x := 0; x < maxFieldCount; x++ {
|
||||||
field := retType.Field(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 has lazy:"key" tag and tags are present inject primary key
|
||||||
if field.Tag.Get(LAZY_TAG) == KEY_VALUE && primaryKey {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//generate random values
|
||||||
nv := kindToRandom(field)
|
nv := kindToRandom(field)
|
||||||
if nv == nil {
|
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)
|
rows[y] = append(rows[y], nv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Mock{
|
return &RowMock{
|
||||||
//sql is rebound and escaped for sqlmock.ExpectQuery
|
//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,
|
Columns: columns,
|
||||||
Rows: rows,
|
Rows: rows,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// converts basic reflect Kind's to psuedo-random data, slices are given a random amount of entries
|
// generates mock data for insert statements to be used for sqlmock, it also returns a slice of
|
||||||
func kindToRandom(field reflect.StructField) any {
|
// example structs with mock data
|
||||||
//this isn't ideal, but since the sql types are structs they were being filled in a weird
|
func RandomStruct(c StructConfig) (*StructMock, error) {
|
||||||
//manner. For now we check those types first, and will clean this up later.
|
//make sure we have an example struct
|
||||||
switch field.Type {
|
if c.Example == nil {
|
||||||
case reflect.TypeOf(sql.NullTime{}):
|
return nil, ErrBadExample
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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()
|
kind := field.Type.Kind()
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Int:
|
case reflect.Int:
|
||||||
@@ -188,11 +188,9 @@ func kindToRandom(field reflect.StructField) any {
|
|||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
return rand.Float64()
|
return rand.Float64()
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return fmt.Sprintf("random %d", rand.Int())
|
return fmt.Sprintf("%d", rand.Int())
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return rand.Int()%2 == 0
|
return rand.Int()%2 == 0
|
||||||
case reflect.TypeOf(time.Time{}).Kind():
|
|
||||||
return time.Now()
|
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
underlying := field.Type.Elem().Kind()
|
underlying := field.Type.Elem().Kind()
|
||||||
count := reflect.ValueOf(field).Len() //fill entire length
|
count := reflect.ValueOf(field).Len() //fill entire length
|
||||||
@@ -277,7 +275,7 @@ func kindToRandom(field reflect.StructField) any {
|
|||||||
case reflect.String:
|
case reflect.String:
|
||||||
var strslice []string
|
var strslice []string
|
||||||
for x := 0; x < count; x++ {
|
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
|
return strslice
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
@@ -291,14 +289,3 @@ func kindToRandom(field reflect.StructField) any {
|
|||||||
|
|
||||||
return nil
|
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