package engine import ( "encoding/json" "fmt" "reflect" "strconv" "git.fsdpf.net/go/condition" "git.fsdpf.net/go/db" _ "git.fsdpf.net/go/db/dialect/sqlite3" db_eng "git.fsdpf.net/go/db/engine" "git.fsdpf.net/go/db/exp" "git.fsdpf.net/go/reflux" "git.fsdpf.net/go/req" "github.com/samber/lo" "github.com/spf13/cast" ) var conn *db.Database var defaultEngineOptions = engineOptions{ debug: false, relations: []string{}, } func init() { database := db_eng.Open(map[string]db_eng.DBConfig{ "condition-engine-sqlite3": db_eng.NewDBConfig("sqlite3", db_eng.WithSQLiteFile(":memory:")), }) conn = database.Connection("condition-engine-sqlite3") } type Engine struct { code string opts engineOptions g req.UserAccessor def func(data reflux.R, g req.UserAccessor) error predicates []*EngineCase } func (this Engine) GetCode() string { return this.code } // 公共参数 func (this *Engine) SetGlobalParams(g req.UserAccessor) *Engine { this.g = g return this } func (this *Engine) Case(cond *condition.Condition, cb func(data reflux.R, g req.UserAccessor) error) *Engine { this.predicates = append(this.predicates, &EngineCase{cond, cb}) return this } // 基础条件 func (this *Engine) Default(cb func(data reflux.R, g req.UserAccessor) error) *Engine { this.def = cb return this } func (this Engine) toField(rv reflect.Value, as string) (db.Expression, error) { if rv.IsZero() { return db.V(nil).As(as), nil } if rv.Kind() == reflect.Interface { rv = rv.Elem() } if rv.Kind() == reflect.Ptr { rv = reflect.Indirect(rv) } v := rv.Interface() switch reflect.TypeOf(v).Kind() { case reflect.Map, reflect.Array, reflect.Slice, reflect.Struct: b, err := json.Marshal(v) if err != nil { return exp.NewSQLFunctionExpression("JSON", nil).As(as), err } return exp.NewSQLFunctionExpression("JSON", string(b)).As(as), err case reflect.String: return db.Cast(db.V(v), "TEXT").As(as), nil case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int8, reflect.Int16, reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint8, reflect.Uint16: return db.Cast(db.V(v), "INTEGER").As(as), nil case reflect.Float64, reflect.Float32: return db.Cast(db.V(v), "REAL").As(as), nil case reflect.Bool: return db.Cast(db.V(v), "BOOL").As(as), nil } return db.V(nil).As(as), nil } func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][]db.Expression, err error) { rt := rv.Type() if rv.Kind() == reflect.Ptr { rv = reflect.Indirect(rv.Elem()) rt = rv.Type() } if tables == nil { tables = map[string][]db.Expression{} } if _, ok := tables[table]; !ok { tables[table] = []db.Expression{} } if rv.Kind() == reflect.Struct { // 遍历结构体的字段 for i := 0; i < rt.NumField(); i++ { if _, ok := tables[rt.Field(i).Name]; !ok && lo.Contains(this.opts.relations, rt.Field(i).Name) { if items, err := this.toTables(rv.Field(i).Elem(), rt.Field(i).Name); err == nil { tables = lo.Assign(tables, items) } else { return nil, err } } else if col, err := this.toField(rv.Field(i), lo.Ternary(rt.Field(i).Tag.Get("db") != "", rt.Field(i).Tag.Get("db"), rt.Field(i).Name)); err != nil { return nil, err } else { tables[table] = append(tables[table], col) } } } else if rv.Kind() == reflect.Map { iter := rv.MapRange() for iter.Next() { if _, ok := tables[iter.Key().String()]; !ok && lo.Contains(this.opts.relations, iter.Key().String()) { if items, err := this.toTables(iter.Value().Elem(), iter.Key().String()); err == nil { tables = lo.Assign(tables, items) } else { return nil, err } } else if col, err := this.toField(iter.Value(), iter.Key().String()); err != nil { return nil, err } else { tables[table] = append(tables[table], col) } } } else { return nil, fmt.Errorf("data type not map/struct, %s", rv.Kind()) } return tables, nil } func (this *Engine) Execute(data any) error { rfx := reflux.New(data) tables, err := this.toTables(rfx.Raw(), this.GetCode()) if err != nil { return err } ts := []any{} // 加载数据 for table, columns := range tables { ts = append(ts, db.Select(this.toSelectCols(columns)...).As(table)) } _db := conn.From(ts...) if this.g == nil { this.g = req.NewGlobalParams(`{}`, nil) } param := condition.NewTokenValue(this.g) cols := []any{} // 加载条件 for i, p := range this.predicates { cols = append(cols, exp.NewSQLFunctionExpression("IFNULL", p.ToSql(param), 0).As(strconv.Itoa(i))) } _db = _db.Select(cols...) if this.opts.debug { fmt.Println(_db.ToSQL()) } result, err := _db.Executor().GetRecord() if err != nil { sql, _, _ := _db.ToSQL() return fmt.Errorf("%s => %s", err, sql) } isDefault := true for fnIndex, v := range result { b := cast.ToInt64(v) if b == 0 { continue } isDefault = false if err := this.predicates[cast.ToInt64(fnIndex)].Execute(rfx, this.g); err != nil { return fmt.Errorf("case %q error, %s", fnIndex, err) } } if isDefault && this.def != nil { if err := this.def(rfx, this.g); err != nil { return fmt.Errorf("case %q error, %s", "default", err) } } return nil } func (this *Engine) toSelectCols(cols []db.Expression) []any { return lo.Map(cols, func(item db.Expression, _ int) any { return item }) } func New(table string, opt ...EngineOption) *Engine { opts := defaultEngineOptions for _, o := range opt { o.apply(&opts) } return &Engine{ code: table, opts: opts, // g: req.NewParam("", nil), } }