schema

package
v1.8.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2026 License: Apache-2.0 Imports: 16 Imported by: 8

README

cosgo/schema

结构体元数据 + 字段访问缓存。被 cosmo / updater 等 ORM 层用作反射底座。

设计目标

  1. 运行期零 lock:所有 Parse 缓存用 sync.Map,读者命中快路径约 80 ns。
  2. 首次并发构建 μs 级唤醒:initDone chan struct{} 替代 ms 级轮询。
  3. 字段访问编译化:getValueFn/setValueFn 在 Schema 构建期预热,运行期是纯函数调用(非反射每次查 Index)。
  4. 多别名统一查找:Go 字段名 / db 标签 / json 标签 任一命中即返回,单次 map 查询。

快速开始

type User struct {
    ID   int64  `json:"id"   bson:"_id"`
    Name string `json:"name" bson:"name"`
    Age  int    `json:"age"  bson:"age"`
}

// 解析(结果自动缓存)
sch, err := schema.Parse(&User{})
if err != nil {
    panic(err)
}

// 按任意别名查字段
f := sch.LookUpField("Name")   // Go 名
f := sch.LookUpField("_id")    // db 名
f := sch.LookUpField("age")    // json 名

// 读写值
u := &User{}
name := sch.GetValue(u, "Name")         // any
_ = sch.SetValue(u, "alice", "Name")    // error

启动期预热(推荐)

高并发场景下,首次访问新类型时 Parse 会导致其它请求等待构建完成。启动时调用一次预热可完全消除这个等待:

func main() {
    if err := schema.Warm(
        &model.User{}, &model.Order{}, &model.Product{},
    ); err != nil {
        log.Fatal(err)
    }
    // ... 后续 cosmo/updater 不再触发同步构建等待
}

// 自定义 Options 版本
schema.WarmWithOptions(opts, &T1{}, &T2{})

Schema 结构

type Schema struct {
    Name           string             // Go 类型名
    Table          string             // 表名(Tabler 接口 / TableName 生成规则 / special)
    Embedded       []*Field           // 匿名嵌入字段
    ModelType      reflect.Type       // 原始 Go 类型
    Fields         map[string]*Field  // 按 Go 字段名(含嵌入提升)的全量索引,公开
    // 以下为私有实现细节:
    // unifiedFields  map[string]*Field  LookUpField 的单次查询索引(Go 名/db/json 任一)
    // dbFields       []*Field           带 db 标签字段的有序列表(Schema.Range 用)
    // initDone       chan struct{}      并发构建同步信号
}
别名优先级(LookUpField)

Go 字段名 > db 标签 > json 标签。冲突时先匹配的字段返回,后来者不覆盖。

API 参考

解析
Parse(dest any) (*Schema, error)
GetOrParse(dest any, opts *Options) (*Schema, error)
ParseWithSpecialTableName(dest any, tableName string, opts *Options) (*Schema, error)

Warm(dests ...any) error
WarmWithOptions(opts *Options, dests ...any) error
Schema 方法
sch.LookUpField(name string) *Field
sch.GetValue(obj any, key string, keys ...any) any
sch.SetValue(obj any, val any, key string, keys ...any) error
sch.Range(cb func(*Field) bool)    // 遍历带 db 标签的字段
sch.JSName(k string) string
sch.New() reflect.Value             // 创建 ModelType 的新实例
sch.Make() reflect.Value            // 创建 []*ModelType 的新切片
Field 方法
f.Name                    // Go 字段名
f.FieldType               // reflect.Type
f.IndirectFieldType       // 解引用后的 Type
f.Index                   // 字段路径(含嵌入提升后的完整路径)
f.StructField             // 原始 reflect.StructField
f.Schema                  // 所属 Schema
f.Embedded                // 嵌入子对象的 Schema(仅 struct 类型字段)

f.JSName() string         // json 标签(缓存)
f.DBName() string         // bson 或 json 标签(缓存)
f.GetName(tags ...) string

f.Get(v reflect.Value) reflect.Value
f.Set(v reflect.Value, val any) error

性能

实测基准(AMD Ryzen AI 9 HX 370)
操作 ns/op allocs/op B/op
LookUpField("Name") (Go 名) 5.6 0 0
LookUpField("_id") (db 名) 5.6 0 0
LookUpField(miss) 4.0 0 0
GetValue(obj, "Name") 35 1 16
SetValue(obj, "alice", "Name") 19 0 0
Parse 缓存命中 19 0 0
ParseWithSpecialTableName 缓存命中 69 2 96
优化要点
  1. 字段访问函数预编译:buildFieldMappings 末尾遍历 Fields 调用 compileGetValueFn/compileSetValueFn,运行期调用时是直接的 closure 调用,不走 field.ReflectValueOf + reflect.Indirect + Field(i) 动态流程。
  2. LookUpField 单次 map:unifiedFields 在构建期按优先级合并 Go/db/json 三套别名到一张 map。
  3. GetValue/SetValue 单 key 快路径:common case 不做 append([]any{key}, keys...)
  4. Parse 缓存键 struct 化:带 specialTableName 时用 struct{t reflect.Type; name string},避免 fmt.Sprintf("%p-%s", ...) 的字符串分配。
  5. cache-hit 快路径:先 Store.Load,命中立即返回;未命中才 make(Schema{})LoadOrStore,消除 cache 命中路径的 Schema 分配。
  6. defer/recover 隔离:缓存命中在 defer 之前返回,避免闭包逃逸导致的堆分配。parseSchemaSlow 独立函数承载完整解析逻辑和 panic 保护,仅缓存未命中时调用。

并发构建的 chan 信号机制

多 goroutine 同时 Parse 同一新类型时:

G1: Load miss → LoadOrStore wins → 持 Schema,开始 Init
G2: Load hit (G1 刚 Store) → waitSchemaInit(s)
G3: 同上
  ...
G1: Init 完成 → defer close(s.initDone) → 所有等待者被唤醒
G2/G3: <-s.initDone 返回 → return (s, s.err)

唤醒延迟是 OS 线程调度级(μs),不是轮询级(ms)。

超时兜底:SchemaInitTimeout = 30s,防御同 goroutine 自引用 struct 造成的死锁。可用 schema.SchemaInitTimeout = ... 调整。

嵌入字段的注意事项

匿名嵌入字段会被提升到外层 Schema 的 Fields 中,但存的是副本(Index 已调整为外层视角)。这些副本在构建期由 warmupFieldFns 按新 Index 重新编译访问函数,保证访问的是真正的嵌入字段而不是同索引的外层字段。

type Inner struct { Name string }
type Outer struct {
    ID int
    Inner  // 匿名嵌入,Name 被提升
}

sch, _ := schema.Parse(&Outer{})
nameField := sch.LookUpField("Name")
// nameField.Index = [1, 0]  (Outer 的第 1 个字段 → Inner 的第 0 个字段)
// 通过 nameField.Get(outerValue) 可以正确拿到 Outer.Inner.Name

配置

opts := schema.New(&NamingStrategy{TablePrefix: "t_"})
opts.Store = &sync.Map{}              // 自定义缓存(通常不需要)
sch, _ := opts.Parse(&User{})

默认 config = schema.New(),被所有无 opts 的调用共享。

错误

  • ErrUnsupportedDataType — dest 不是结构体
  • ErrDuplicateDBName — 同一 Schema 内两个字段的 db 标签冲突
  • schema parse panic: ... — 构建期 panic 被 recover 转换为 error 返回
  • schema init timeout after 30s ... — 自引用 struct 或 init 失败导致无限等待

Documentation

Index

Constants

View Source
const (
	IndexTag      = "index"
	IndexName     = "NAME"
	IndexSort     = "SORT"
	IndexUnique   = "UNIQUE"
	IndexSparse   = "SPARSE"
	IndexPartial  = "PARTIAL" //部分索引 index:"PARTIAL:{\"invite\":{\"$gt\":0}}"`
	IndexPriority = "PRIORITY"
)

https://www.mongodb.com/zh-cn/docs/manual/core/index-partial/#std-label-index-type-partial 部分索引 使用一条查询语句

Variables

View Source
var (
	ErrUnsupportedDataType = errors.New("unsupported data type")
	ErrDuplicateDBName     = errors.New("duplicate database field name")
)
View Source
var SchemaInitTimeout = 30 * time.Second

SchemaInitTimeout 等待其他 goroutine 完成 Schema 初始化的最大时长。 仅用于防御同 goroutine 自引用 struct 造成的死锁;正常并发构建等待是 μs 级, 基本不会触发此上限。超时后返回明确错误,避免静默卡住。

Functions

func Kind

func Kind(dest any) reflect.Type

Kind 获取接口或指针指向的底层类型

func NewDuplicateDBNameError added in v1.6.8

func NewDuplicateDBNameError(structName, fieldName1, fieldName2 string) error

func NewUnsupportedDataTypeError added in v1.6.8

func NewUnsupportedDataTypeError(dest any) error

func ParseTagSetting

func ParseTagSetting(str string, sep string) map[string]string

ParseTagSetting 解析结构体标签设置

func ToArray

func ToArray(v any) (r []any)

ToArray 将任意类型转换为 []any

func ToInt added in v1.1.0

func ToInt(i any) (r int64)

ToInt 将任意类型转换为 int64

func ToInt32 added in v1.1.0

func ToInt32(i any) int32

ToInt32 将任意类型转换为 int32

func ToString

func ToString(value any) string

ToString 将任意类型转换为字符串

func ValueOf

func ValueOf(i any) reflect.Value

ValueOf 获取接口或值的 reflect.Value

func Warm added in v1.8.0

func Warm(dests ...any) error

Warm 在应用启动阶段预先解析一批类型,以消除请求期首次访问时的 Schema 构建等待。 传入每个类型的零值指针(如 &User{}, &Order{}),使用默认 Options。 遇到任一解析错误立即返回该错误,已成功的 Schema 会保留在缓存中。

典型用法:

func main() {
    if err := schema.Warm(&User{}, &Order{}, &Product{}); err != nil {
        log.Fatal(err)
    }
    // ... 后续请求不会再触发同步构建等待
}

func WarmWithOptions added in v1.8.0

func WarmWithOptions(opts *Options, dests ...any) error

WarmWithOptions 与 Warm 相同,但使用自定义 Options。 对每个 dest 调用 opts.Parse(dest),发生错误立即返回。

Types

type Field

type Field struct {
	// 核心字段:字段标识和类型信息
	Name              string              // 字段名称
	FieldType         reflect.Type        // 字段类型
	IndirectFieldType reflect.Type        // 间接字段类型(去除指针)
	StructField       reflect.StructField // 原始结构体字段

	// 关联信息:与 Schema 和嵌入字段的关系
	Schema   *Schema // 所在的父 Schema
	Embedded *Schema // 嵌入子对象的 Schema

	// 访问信息:字段访问路径和缓存
	Index []int // 字段索引路径
	// contains filtered or unexported fields
}

Field 表示结构体的一个字段,包含字段的元数据和操作方法

func (*Field) DBName

func (field *Field) DBName() string

func (*Field) Get added in v1.1.0

func (field *Field) Get(value reflect.Value) reflect.Value

func (*Field) GetEmbeddedFields added in v1.1.0

func (field *Field) GetEmbeddedFields() (r []*Field)

func (*Field) GetName added in v1.2.0

func (field *Field) GetName(ks ...string) string

GetName json,form,.... 按照ks 指定的顺寻找以名字,所有TAG 都无法匹配时返回字段名

func (*Field) GetTagName added in v1.2.0

func (field *Field) GetTagName(k string) string

func (*Field) JSName added in v1.2.0

func (field *Field) JSName() string

func (*Field) ReflectValueOf

func (field *Field) ReflectValueOf(value reflect.Value) reflect.Value

func (*Field) Set

func (field *Field) Set(value reflect.Value, v any) error

Set valuer, setter when parse struct

func (*Field) SetBool added in v1.1.0

func (field *Field) SetBool(value reflect.Value, v any) error

func (*Field) SetFloat added in v1.1.0

func (field *Field) SetFloat(value reflect.Value, v any) error

func (*Field) SetInt added in v1.1.0

func (field *Field) SetInt(value reflect.Value, v any) error

func (*Field) SetString added in v1.1.0

func (field *Field) SetString(value reflect.Value, v any) error

func (*Field) SetUint added in v1.1.0

func (field *Field) SetUint(value reflect.Value, v any) error

type GetIndexes added in v1.8.1

type GetIndexes interface {
	GetIndexes() []*IndexField
}

type Index

type Index struct {
	Name    string
	Unique  bool //唯一
	Sparse  bool //稀疏索引
	Partial []string
	Fields  []*IndexField
	// contains filtered or unexported fields
}

func (*Index) Build

func (this *Index) Build(whereParser ...IndexPartialParse) (index *mongo.IndexModel, err error)

type IndexField

type IndexField struct {
	Sort     string   // DESC, ASC
	Name     string   // index name
	DBName   []string // a.b.c
	Unique   bool     //唯一
	Sparse   bool     //稀疏索引:仅包含具有索引字段的文档,节省存储空间,适用于字段存在性较少的场景
	Partial  string   //部分索引:基于指定的过滤条件创建索引,只包含满足条件的文档,提高查询性能并减少存储开销,格式如:{"rating":{"$gt":5}}
	Priority int      //排序字段之间的排序ASC
}

func (*IndexField) GetDBName

func (this *IndexField) GetDBName() string

type IndexPartialParse added in v1.2.0

type IndexPartialParse func(*Schema, []string) (any, error)

type Indexes added in v1.8.1

type Indexes map[string]*Index

func (Indexes) AddField added in v1.8.1

func (is Indexes) AddField(sch *Schema, field *IndexField)

type Namer

type Namer interface {
	TableName(table string) string
	ColumnName(table, column string) string
}

Namer Namer interface

type NamingStrategy

type NamingStrategy struct {
	TablePrefix   string
	SingularTable bool
	NameReplacer  Replacer
	NoLowerCase   bool
}

NamingStrategy tables, columns naming strategy

func (NamingStrategy) ColumnName

func (ns NamingStrategy) ColumnName(table, column string) string

ColumnName convert string to column name

func (NamingStrategy) TableName

func (ns NamingStrategy) TableName(str string) string

TableName convert string to table name

type Options

type Options struct {
	Namer
	Store *sync.Map
}

func New

func New(namer ...Namer) (i *Options)

New 新封装schema Store Namer

func (*Options) Parse

func (opts *Options) Parse(dest any) (*Schema, error)

func (*Options) ParseWithSpecialTableName

func (opts *Options) ParseWithSpecialTableName(dest any, name string) (*Schema, error)

type Replacer

type Replacer interface {
	Replace(name string) string
}

Replacer replacer interface like strings.Replacer

type Schema

type Schema struct {
	Name      string
	Table     string
	Embedded  []*Field //匿名嵌入字段的 Field 列表
	ModelType reflect.Type
	// Fields 按 Go 字段名(含嵌入提升)索引的全量字段表,公开访问。
	Fields map[string]*Field
	// contains filtered or unexported fields
}

func GetOrParse added in v1.1.0

func GetOrParse(dest any, opts *Options) (*Schema, error)

GetOrParse 从目标结构体解析 Schema 信息,支持自定义选项

参数:

dest: 目标结构体实例或指针,用于解析其字段信息
opts: 解析选项,包含缓存、表名生成等配置

返回:

*Schema: 解析生成的 Schema 实例,包含结构体的所有字段信息
error: 解析过程中发生的错误,如类型不支持等

示例:

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}
opts := &schema.Options{...}
schema, err := schema.GetOrParse(&User{}, opts)

func Parse

func Parse(dest any) (*Schema, error)

Parse 从目标结构体解析 Schema 信息

参数:

dest: 目标结构体实例或指针,用于解析其字段信息

返回:

*Schema: 解析生成的 Schema 实例,包含结构体的所有字段信息
error: 解析过程中发生的错误,如类型不支持等

示例:

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}
schema, err := schema.Parse(&User{})

func ParseWithSpecialTableName

func ParseWithSpecialTableName(dest any, specialTableName string, opts *Options) (*Schema, error)

ParseWithSpecialTableName 从目标结构体解析 Schema 信息,支持自定义表名。 缓存命中时走零分配快路径(不设置 defer/recover),仅缓存未命中时进入完整解析。

func (*Schema) GetValue

func (schema *Schema) GetValue(obj any, key string, keys ...any) (r any)

GetValue 按路径取值。单 key 的 common case(len(keys)==0)走快路径,不分配合并 slice。

func (*Schema) JSName added in v1.2.0

func (schema *Schema) JSName(k string) (r string)

func (*Schema) LookIndex

func (schema *Schema) LookIndex(name string) *Index

func (*Schema) LookUpField

func (schema *Schema) LookUpField(name string) *Field

LookUpField 按名字查找字段,支持 Go 字段名、db 标签名、json 标签名任一(优先级依此顺序)。 通过构建期合并的 unifiedFields 单次 map 查询完成。

func (*Schema) Make added in v1.1.0

func (schema *Schema) Make() reflect.Value

Make Make a Slice

func (*Schema) New

func (schema *Schema) New() reflect.Value

func (*Schema) ParseField

func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field

func (*Schema) ParseIndexes

func (schema *Schema) ParseIndexes() map[string]*Index

ParseIndexes parse schema indexes

func (*Schema) Range added in v1.1.0

func (schema *Schema) Range(cb func(*Field) bool)

Range 按注册顺序遍历所有带 db 标签的字段(用于 ORM 持久化场景)。 slice 迭代比 map 更快,且顺序稳定。

func (*Schema) SetValue

func (schema *Schema) SetValue(obj any, val any, key string, keys ...any) (err error)

SetValue 按路径赋值。单 key 的 common case 走快路径,不分配合并 slice。

func (*Schema) String

func (schema *Schema) String() string

type Tabler

type Tabler interface {
	TableName() string
}

Directories

Path Synopsis
test
comprehensive command
simple command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL