internal/queryset/field/field.go
package field
import (
"fmt"
"go/types"
"reflect"
"strings"
"github.com/jinzhu/gorm"
)
type BaseInfo struct {
Name string // name of field
DBName string // name of field in DB
TypeName string // name of type of field
IsStruct bool
IsNumeric bool
IsTime bool
IsString bool
}
type Info struct {
pointed *BaseInfo
BaseInfo
IsPointer bool
}
func (fi Info) GetPointed() Info {
return Info{
BaseInfo: *fi.pointed,
}
}
type InfoGenerator struct {
pkg *types.Package
}
type Field interface {
Name() string
Type() types.Type
Tag() reflect.StructTag
}
type field struct {
name string
typ types.Type
tag reflect.StructTag
}
func (f field) Name() string {
return f.name
}
func (f field) Type() types.Type {
return f.typ
}
func (f field) Tag() reflect.StructTag {
return f.tag
}
func NewInfoGenerator(pkg *types.Package) *InfoGenerator {
return &InfoGenerator{
pkg: pkg,
}
}
func (g InfoGenerator) getOriginalTypeName(t *types.Named) string {
if t.Obj().Pkg() == g.pkg {
// t is from the same package as a struct
return t.Obj().Name()
}
// t is an imported from another package type
return fmt.Sprintf("%s.%s", t.Obj().Pkg().Name(), t.Obj().Name())
}
// parseTagSetting is copy-pasted from gorm source code.
func parseTagSetting(tags reflect.StructTag) map[string]string {
setting := map[string]string{}
for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} {
tags := strings.Split(str, ";")
for _, value := range tags {
v := strings.Split(value, ":")
k := strings.TrimSpace(strings.ToUpper(v[0]))
if len(v) >= 2 {
setting[k] = strings.Join(v[1:], ":")
} else {
setting[k] = k
}
}
}
return setting
}
func (g InfoGenerator) GenFieldInfo(f Field) *Info {
tagSetting := parseTagSetting(f.Tag())
if tagSetting["-"] != "" { // skipped by tag field
return nil
}
dbName := gorm.ToDBName(f.Name())
if dbColName := tagSetting["COLUMN"]; dbColName != "" {
dbName = dbColName
}
bi := BaseInfo{
Name: f.Name(),
TypeName: f.Type().String(),
DBName: dbName,
}
if bi.TypeName == "time.Time" {
bi.IsTime = true
bi.IsNumeric = true
return &Info{
BaseInfo: bi,
}
}
switch t := f.Type().(type) {
case *types.Basic:
bi.IsString = t.Info()&types.IsString != 0
bi.IsNumeric = t.Info()&types.IsNumeric != 0
return &Info{
BaseInfo: bi,
}
case *types.Slice:
if t.Elem().String() == "byte" {
return &Info{
BaseInfo: bi,
}
}
return nil
case *types.Named:
r := g.GenFieldInfo(field{
name: f.Name(),
typ: t.Underlying(),
tag: f.Tag(),
})
if r != nil {
r.TypeName = g.getOriginalTypeName(t)
}
return r
case *types.Struct:
bi.IsStruct = true
return &Info{
BaseInfo: bi,
}
case *types.Pointer:
pf := g.GenFieldInfo(field{
name: f.Name(),
typ: t.Elem(),
tag: f.Tag(),
})
return &Info{
BaseInfo: bi,
IsPointer: true,
pointed: &pf.BaseInfo,
}
default:
// no filtering is needed
return nil
}
}