fork github.com/doug-martin

This commit is contained in:
2025-03-22 23:02:05 +08:00
commit f14642a736
131 changed files with 34555 additions and 0 deletions
+19
View File
@@ -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
}
+101
View File
@@ -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
}
+51
View File
@@ -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
}
+135
View File
@@ -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...)
}
+217
View File
@@ -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
+33
View File
@@ -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, ","))
}