fork github.com/doug-martin
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Error struct {
|
||||
err string
|
||||
}
|
||||
|
||||
func New(message string, args ...interface{}) error {
|
||||
return Error{err: "db: " + fmt.Sprintf(message, args...)}
|
||||
}
|
||||
|
||||
func NewEncodeError(t interface{}) error {
|
||||
return Error{err: "db_encode_error: " + fmt.Sprintf("Unable to encode value %+v", t)}
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.err
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package sb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// Builder that is composed of a bytes.Buffer. It is used internally and by adapters to build SQL statements
|
||||
type (
|
||||
SQLBuilder interface {
|
||||
Error() error
|
||||
SetError(err error) SQLBuilder
|
||||
WriteArg(i ...interface{}) SQLBuilder
|
||||
Write(p []byte) SQLBuilder
|
||||
WriteStrings(ss ...string) SQLBuilder
|
||||
WriteRunes(r ...rune) SQLBuilder
|
||||
IsPrepared() bool
|
||||
CurrentArgPosition() int
|
||||
ToSQL() (sql string, args []interface{}, err error)
|
||||
}
|
||||
sqlBuilder struct {
|
||||
buf *bytes.Buffer
|
||||
// True if the sql should not be interpolated
|
||||
isPrepared bool
|
||||
// Current Number of arguments, used by adapters that need positional placeholders
|
||||
currentArgPosition int
|
||||
args []interface{}
|
||||
err error
|
||||
}
|
||||
)
|
||||
|
||||
func NewSQLBuilder(isPrepared bool) SQLBuilder {
|
||||
return &sqlBuilder{
|
||||
buf: &bytes.Buffer{},
|
||||
isPrepared: isPrepared,
|
||||
args: make([]interface{}, 0),
|
||||
currentArgPosition: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *sqlBuilder) Error() error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
func (b *sqlBuilder) SetError(err error) SQLBuilder {
|
||||
if b.err == nil {
|
||||
b.err = err
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *sqlBuilder) Write(bs []byte) SQLBuilder {
|
||||
if b.err == nil {
|
||||
b.buf.Write(bs)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *sqlBuilder) WriteStrings(ss ...string) SQLBuilder {
|
||||
if b.err == nil {
|
||||
for _, s := range ss {
|
||||
b.buf.WriteString(s)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *sqlBuilder) WriteRunes(rs ...rune) SQLBuilder {
|
||||
if b.err == nil {
|
||||
for _, r := range rs {
|
||||
b.buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Returns true if the sql is a prepared statement
|
||||
func (b *sqlBuilder) IsPrepared() bool {
|
||||
return b.isPrepared
|
||||
}
|
||||
|
||||
// Returns true if the sql is a prepared statement
|
||||
func (b *sqlBuilder) CurrentArgPosition() int {
|
||||
return b.currentArgPosition
|
||||
}
|
||||
|
||||
// Adds an argument to the builder, used when IsPrepared is false
|
||||
func (b *sqlBuilder) WriteArg(i ...interface{}) SQLBuilder {
|
||||
if b.err == nil {
|
||||
b.currentArgPosition += len(i)
|
||||
b.args = append(b.args, i...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Returns the sql string, and arguments.
|
||||
func (b *sqlBuilder) ToSQL() (sql string, args []interface{}, err error) {
|
||||
if b.err != nil {
|
||||
return sql, args, b.err
|
||||
}
|
||||
return b.buf.String(), b.args, nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "json"
|
||||
// tag, or the empty string. It does not include the leading comma.
|
||||
type Options string
|
||||
|
||||
func New(tagName string, st reflect.StructTag) Options {
|
||||
return Options(st.Get(tagName))
|
||||
}
|
||||
|
||||
func (o Options) Values() []string {
|
||||
if string(o) == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(string(o), ",")
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o Options) Contains(optionName string) bool {
|
||||
if o.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
values := o.Values()
|
||||
for _, s := range values {
|
||||
if s == optionName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o Options) Equals(val string) bool {
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
return string(o) == val
|
||||
}
|
||||
|
||||
func (o Options) IsEmpty() bool {
|
||||
return len(o) == 0
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.fsdpf.net/go/db/v2/internal/tag"
|
||||
)
|
||||
|
||||
type (
|
||||
ColumnData struct {
|
||||
ColumnName string
|
||||
FieldIndex []int
|
||||
ShouldInsert bool
|
||||
ShouldUpdate bool
|
||||
DefaultIfEmpty bool
|
||||
OmitNil bool
|
||||
OmitEmpty bool
|
||||
GoType reflect.Type
|
||||
}
|
||||
ColumnMap map[string]ColumnData
|
||||
)
|
||||
|
||||
func newColumnMap(t reflect.Type, fieldIndex []int, prefixes []string) ColumnMap {
|
||||
cm, n := ColumnMap{}, t.NumField()
|
||||
var subColMaps []ColumnMap
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
if f.Anonymous && (f.Type.Kind() == reflect.Struct || f.Type.Kind() == reflect.Ptr) {
|
||||
dbTag := tag.New("db", f.Tag)
|
||||
if !dbTag.Contains("-") {
|
||||
subColMaps = append(subColMaps, getStructColumnMap(&f, fieldIndex, dbTag.Values(), prefixes))
|
||||
}
|
||||
} else if f.PkgPath == "" {
|
||||
dbTag := tag.New("db", f.Tag)
|
||||
// if PkgPath is empty then it is an exported field
|
||||
columnName := getColumnName(&f, dbTag)
|
||||
if !shouldIgnoreField(dbTag) {
|
||||
if !implementsScanner(f.Type) {
|
||||
subCm := getStructColumnMap(&f, fieldIndex, []string{columnName}, prefixes)
|
||||
if len(subCm) != 0 {
|
||||
subColMaps = append(subColMaps, subCm)
|
||||
continue
|
||||
}
|
||||
}
|
||||
ffTag := tag.New("ff", f.Tag)
|
||||
columnName = strings.Join(append(prefixes, columnName), ".")
|
||||
cm[columnName] = newColumnData(&f, columnName, fieldIndex, ffTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cm.Merge(subColMaps)
|
||||
}
|
||||
|
||||
func (cm ColumnMap) Cols() []string {
|
||||
structCols := make([]string, 0, len(cm))
|
||||
for key := range cm {
|
||||
structCols = append(structCols, key)
|
||||
}
|
||||
sort.Strings(structCols)
|
||||
return structCols
|
||||
}
|
||||
|
||||
func (cm ColumnMap) Merge(colMaps []ColumnMap) ColumnMap {
|
||||
for _, subCm := range colMaps {
|
||||
for key, val := range subCm {
|
||||
if _, ok := cm[key]; !ok {
|
||||
cm[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
func implementsScanner(t reflect.Type) bool {
|
||||
if IsPointer(t.Kind()) {
|
||||
t = t.Elem()
|
||||
}
|
||||
if reflect.PtrTo(t).Implements(scannerType) {
|
||||
return true
|
||||
}
|
||||
if !IsStruct(t.Kind()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func newColumnData(f *reflect.StructField, columnName string, fieldIndex []int, ffTag tag.Options) ColumnData {
|
||||
return ColumnData{
|
||||
ColumnName: columnName,
|
||||
ShouldInsert: !ffTag.Contains(skipInsertTagName),
|
||||
ShouldUpdate: !ffTag.Contains(skipUpdateTagName),
|
||||
DefaultIfEmpty: ffTag.Contains(defaultIfEmptyTagName),
|
||||
OmitNil: ffTag.Contains(omitNilTagName),
|
||||
OmitEmpty: ffTag.Contains(omitEmptyTagName),
|
||||
FieldIndex: concatFieldIndexes(fieldIndex, f.Index),
|
||||
GoType: f.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func getStructColumnMap(f *reflect.StructField, fieldIndex []int, fieldNames, prefixes []string) ColumnMap {
|
||||
subFieldIndexes := concatFieldIndexes(fieldIndex, f.Index)
|
||||
subPrefixes := prefixes
|
||||
subPrefixes = append(subPrefixes, fieldNames...)
|
||||
if f.Type.Kind() == reflect.Ptr {
|
||||
return newColumnMap(f.Type.Elem(), subFieldIndexes, subPrefixes)
|
||||
}
|
||||
return newColumnMap(f.Type, subFieldIndexes, subPrefixes)
|
||||
}
|
||||
|
||||
func getColumnName(f *reflect.StructField, dbTag tag.Options) string {
|
||||
if dbTag.IsEmpty() {
|
||||
return columnRenameFunction(f.Name)
|
||||
}
|
||||
return dbTag.Values()[0]
|
||||
}
|
||||
|
||||
func shouldIgnoreField(dbTag tag.Options) bool {
|
||||
if dbTag.Equals("-") {
|
||||
return true
|
||||
} else if dbTag.IsEmpty() && ignoreUntaggedFields {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// safely concat two fieldIndex slices into one.
|
||||
func concatFieldIndexes(fieldIndexPath, fieldIndex []int) []int {
|
||||
fieldIndexes := make([]int, 0, len(fieldIndexPath)+len(fieldIndex))
|
||||
fieldIndexes = append(fieldIndexes, fieldIndexPath...)
|
||||
return append(fieldIndexes, fieldIndex...)
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.fsdpf.net/go/db/v2/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
skipUpdateTagName = "skipupdate"
|
||||
skipInsertTagName = "skipinsert"
|
||||
defaultIfEmptyTagName = "defaultifempty"
|
||||
omitNilTagName = "omitnil"
|
||||
omitEmptyTagName = "omitempty"
|
||||
)
|
||||
|
||||
var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||
|
||||
func IsUint(k reflect.Kind) bool {
|
||||
return (k == reflect.Uint) ||
|
||||
(k == reflect.Uint8) ||
|
||||
(k == reflect.Uint16) ||
|
||||
(k == reflect.Uint32) ||
|
||||
(k == reflect.Uint64)
|
||||
}
|
||||
|
||||
func IsInt(k reflect.Kind) bool {
|
||||
return (k == reflect.Int) ||
|
||||
(k == reflect.Int8) ||
|
||||
(k == reflect.Int16) ||
|
||||
(k == reflect.Int32) ||
|
||||
(k == reflect.Int64)
|
||||
}
|
||||
|
||||
func IsFloat(k reflect.Kind) bool {
|
||||
return (k == reflect.Float32) ||
|
||||
(k == reflect.Float64)
|
||||
}
|
||||
|
||||
func IsString(k reflect.Kind) bool {
|
||||
return k == reflect.String
|
||||
}
|
||||
|
||||
func IsBool(k reflect.Kind) bool {
|
||||
return k == reflect.Bool
|
||||
}
|
||||
|
||||
func IsSlice(k reflect.Kind) bool {
|
||||
return k == reflect.Slice
|
||||
}
|
||||
|
||||
func IsStruct(k reflect.Kind) bool {
|
||||
return k == reflect.Struct
|
||||
}
|
||||
|
||||
func IsInvalid(k reflect.Kind) bool {
|
||||
return k == reflect.Invalid
|
||||
}
|
||||
|
||||
func IsPointer(k reflect.Kind) bool {
|
||||
return k == reflect.Ptr
|
||||
}
|
||||
|
||||
func IsNil(v reflect.Value) bool {
|
||||
if !v.IsValid() {
|
||||
return true
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
|
||||
return v.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func IsEmptyValue(v reflect.Value) bool {
|
||||
return !v.IsValid() || v.IsZero()
|
||||
}
|
||||
|
||||
var (
|
||||
structMapCache = make(map[interface{}]ColumnMap)
|
||||
structMapCacheLock = sync.Mutex{}
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultColumnRenameFunction = strings.ToLower
|
||||
columnRenameFunction = DefaultColumnRenameFunction
|
||||
ignoreUntaggedFields = false
|
||||
)
|
||||
|
||||
func SetIgnoreUntaggedFields(ignore bool) {
|
||||
// If the value here is changing, reset the struct map cache
|
||||
if ignore != ignoreUntaggedFields {
|
||||
ignoreUntaggedFields = ignore
|
||||
|
||||
structMapCacheLock.Lock()
|
||||
defer structMapCacheLock.Unlock()
|
||||
|
||||
structMapCache = make(map[interface{}]ColumnMap)
|
||||
}
|
||||
}
|
||||
|
||||
func SetColumnRenameFunction(newFunction func(string) string) {
|
||||
columnRenameFunction = newFunction
|
||||
}
|
||||
|
||||
// GetSliceElementType returns the type for a slices elements.
|
||||
func GetSliceElementType(val reflect.Value) reflect.Type {
|
||||
elemType := val.Type().Elem()
|
||||
if elemType.Kind() == reflect.Ptr {
|
||||
elemType = elemType.Elem()
|
||||
}
|
||||
|
||||
return elemType
|
||||
}
|
||||
|
||||
// AppendSliceElement will append val to slice. Handles slice of pointers and
|
||||
// not pointers. Val needs to be a pointer.
|
||||
func AppendSliceElement(slice, val reflect.Value) {
|
||||
if slice.Type().Elem().Kind() == reflect.Ptr {
|
||||
slice.Set(reflect.Append(slice, val))
|
||||
} else {
|
||||
slice.Set(reflect.Append(slice, reflect.Indirect(val)))
|
||||
}
|
||||
}
|
||||
|
||||
func GetTypeInfo(i interface{}, val reflect.Value) (reflect.Type, reflect.Kind) {
|
||||
var t reflect.Type
|
||||
valKind := val.Kind()
|
||||
if valKind == reflect.Slice {
|
||||
if reflect.ValueOf(i).Kind() == reflect.Ptr {
|
||||
t = reflect.TypeOf(i).Elem().Elem()
|
||||
} else {
|
||||
t = reflect.TypeOf(i).Elem()
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
valKind = t.Kind()
|
||||
} else {
|
||||
t = val.Type()
|
||||
}
|
||||
return t, valKind
|
||||
}
|
||||
|
||||
func SafeGetFieldByIndex(v reflect.Value, fieldIndex []int) (result reflect.Value, isAvailable bool) {
|
||||
switch len(fieldIndex) {
|
||||
case 0:
|
||||
return v, true
|
||||
case 1:
|
||||
return v.FieldByIndex(fieldIndex), true
|
||||
default:
|
||||
if f := reflect.Indirect(v.Field(fieldIndex[0])); f.IsValid() {
|
||||
return SafeGetFieldByIndex(f, fieldIndex[1:])
|
||||
}
|
||||
}
|
||||
return reflect.ValueOf(nil), false
|
||||
}
|
||||
|
||||
func SafeSetFieldByIndex(v reflect.Value, fieldIndex []int, src interface{}) (result reflect.Value) {
|
||||
v = reflect.Indirect(v)
|
||||
switch len(fieldIndex) {
|
||||
case 0:
|
||||
return v
|
||||
case 1:
|
||||
f := v.FieldByIndex(fieldIndex)
|
||||
srcVal := reflect.ValueOf(src)
|
||||
f.Set(reflect.Indirect(srcVal))
|
||||
default:
|
||||
f := v.Field(fieldIndex[0])
|
||||
switch f.Kind() {
|
||||
case reflect.Ptr:
|
||||
s := f
|
||||
if f.IsNil() || !f.IsValid() {
|
||||
s = reflect.New(f.Type().Elem())
|
||||
f.Set(s)
|
||||
}
|
||||
SafeSetFieldByIndex(reflect.Indirect(s), fieldIndex[1:], src)
|
||||
case reflect.Struct:
|
||||
SafeSetFieldByIndex(f, fieldIndex[1:], src)
|
||||
default: // use the original value
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
type rowData = map[string]interface{}
|
||||
|
||||
// AssignStructVals will assign the data from rd to i.
|
||||
func AssignStructVals(i interface{}, rd rowData, cm ColumnMap) {
|
||||
val := reflect.Indirect(reflect.ValueOf(i))
|
||||
|
||||
for name, data := range cm {
|
||||
src, ok := rd[name]
|
||||
if ok {
|
||||
SafeSetFieldByIndex(val, data.FieldIndex, src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetColumnMap(i interface{}) (ColumnMap, error) {
|
||||
val := reflect.Indirect(reflect.ValueOf(i))
|
||||
t, valKind := GetTypeInfo(i, val)
|
||||
if valKind != reflect.Struct {
|
||||
return nil, errors.New("cannot scan into this type: %v", t) // #nosec
|
||||
}
|
||||
|
||||
structMapCacheLock.Lock()
|
||||
defer structMapCacheLock.Unlock()
|
||||
if _, ok := structMapCache[t]; !ok {
|
||||
structMapCache[t] = newColumnMap(t, []int{}, []string{})
|
||||
}
|
||||
return structMapCache[t], nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ValueSlice []reflect.Value
|
||||
|
||||
func (vs ValueSlice) Len() int { return len(vs) }
|
||||
func (vs ValueSlice) Less(i, j int) bool { return vs[i].String() < vs[j].String() }
|
||||
func (vs ValueSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||
|
||||
func (vs ValueSlice) Equal(other ValueSlice) bool {
|
||||
sort.Sort(other)
|
||||
for i, key := range vs {
|
||||
if other[i].String() != key.String() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (vs ValueSlice) String() string {
|
||||
vals := make([]string, vs.Len())
|
||||
for i, key := range vs {
|
||||
vals[i] = fmt.Sprintf("%q", key.String())
|
||||
}
|
||||
sort.Strings(vals)
|
||||
return fmt.Sprintf("[%s]", strings.Join(vals, ","))
|
||||
}
|
||||
Reference in New Issue
Block a user