Compare commits
18 Commits
f915efb7e5
...
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/
|
||||||
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