From 5b39565a368a2af901a75c18b05533f8f33ee796 Mon Sep 17 00:00:00 2001 From: what Date: Fri, 21 Aug 2026 08:55:21 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84:=20Condition/Engine=20?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=88=B0=20db-v2=20=E7=9A=84=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F=E6=A0=91=EF=BC=8C=E6=9B=BF=E6=8D=A2=E6=8E=89?= =?UTF-8?q?=E6=97=A7=E7=9A=84=20db.Raw=20=E6=8B=BC=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ToSql 之前是手工拼接 SQL 字符串(string 类型的 conditions、手动加括号、 手动拼 AND/OR),改成用 exp.NewExpressionList 构造表达式树,跟 db-v2 其余部分统一走"先建表达式树、由方言渲染成 SQL"这条路,不再自己维护一份 字符串拼接逻辑。ConditionType/ConditionOperator 相应改成 exp.ExpressionListType/ exp.BooleanOperation 的类型别名。 TokenValue 从 condition_expr.go 里独立成 token_value.go,从 GetParam(k string) req.GlobalParams 改成 GetParam(k string) valuex.Accessor (配合 reflux/valuex),跟其它 "-v2" 项目的属性访问方式统一。engine 包 配合适配(sqlite3 用 db/engine 的新连接管理,SelectDataset 替代 db.Connection),删掉了不再需要的 EngineParam(老 GlobalParams 包装, 新 TokenValue 已经不需要这层)。 已知问题:engine.go 在没有外部传入 GlobalParams 时用 nil user 构造兜底 值,跟 NewTokenValue 现在强制要求非 nil user 冲突,TestEngine 会 panic ——这个先不修,等后面调整 Engine.Execute 让调用方能传 user 时一起解决。 --- .gitignore | 1 + condition.go | 36 +++----- condition_expr.go | 192 ++++++++++++++++++++--------------------- engine/engine.go | 141 +++++++++++++++--------------- engine/engine_case.go | 11 +-- engine/engine_param.go | 17 ---- engine/engine_test.go | 55 +++++++----- token_value.go | 53 ++++++++++++ util.go | 39 +++++++++ 9 files changed, 313 insertions(+), 232 deletions(-) delete mode 100644 engine/engine_param.go create mode 100644 token_value.go create mode 100644 util.go diff --git a/.gitignore b/.gitignore index adf8f72..398d22a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ # Go workspace file go.work +.DS_Store diff --git a/condition.go b/condition.go index 64ed2e6..87cb9e7 100644 --- a/condition.go +++ b/condition.go @@ -5,15 +5,16 @@ import ( "strings" "git.fsdpf.net/go/db" + "git.fsdpf.net/go/db/exp" "git.fsdpf.net/go/utils" "github.com/samber/lo" ) -type ConditionType string +type ConditionType = exp.ExpressionListType const ( - OR ConditionType = "OR" - AND ConditionType = "AND" + AND ConditionType = exp.AndType + OR ConditionType = exp.OrType ) type Condition struct { @@ -92,44 +93,35 @@ func (this Condition) IsAlwaysRight() bool { return !lo.Some(flag, []bool{false}) } -// 生成 SQL 语句 +// ToSql 生成 SQL 语句 func (this Condition) ToSql(m TokenValue) db.Expression { - conditions := []string{} + conditions := []exp.Expression{} // 表达式 for _, item := range this.exprs { if item.IsIgnoreEmptyParma(m) { continue } - conditions = append(conditions, string(item.ToSql(m))) + conditions = append(conditions, item.ToSql(m)) } // 条件子集 for _, item := range this.childrens { - conditions = append(conditions, string(item.ToSql(m))) + conditions = append(conditions, item.ToSql(m)) } - // 去除无效的表达式 - conditions = lo.Filter(conditions, func(item string, _ int) bool { return item != "" }) - // 组合 SQL - sql := strings.Join(conditions, " "+string(this.Type())+" ") - if sql == "" { + if this.IsEmpty() { if this.parent == nil && this.Type() == OR { - return db.Raw("false") + return db.V(false) } else if this.parent == nil && this.Type() == AND { - return db.Raw("true") + return db.V(true) } - return db.Raw("") - } - - // 包裹 SQL, 避免语法表达错误 - if this.parent == nil || this.Type() == OR { - sql = "(" + sql + ")" + return nil } if this.describe != "" { - sql += "/*" + this.describe + "*/" + return db.L("? /* ? */", exp.NewExpressionList(this.typ, conditions...), db.L(this.describe)) } - return db.Raw(sql) + return exp.NewExpressionList(this.typ, conditions...) } // 设置条件表达式 diff --git a/condition_expr.go b/condition_expr.go index 17f4827..411cf8c 100644 --- a/condition_expr.go +++ b/condition_expr.go @@ -7,13 +7,13 @@ import ( "strings" "git.fsdpf.net/go/db" - "git.fsdpf.net/go/req" - "github.com/samber/lo" - "github.com/spf13/cast" + "git.fsdpf.net/go/db/exp" + "git.fsdpf.net/go/db/sqlgen" + "git.fsdpf.net/go/reflux/valuex" ) type TokenType string -type ConditionOperator string +type ConditionOperator = exp.BooleanOperation const ( SQL TokenType = "sql" @@ -23,29 +23,24 @@ const ( ) const ( - IS_NULL ConditionOperator = "IS NULL" - IS_NOT_NULL ConditionOperator = "IS NOT NULL" - EQ ConditionOperator = "=" - NE ConditionOperator = "!=" - GT ConditionOperator = ">" - GE ConditionOperator = ">=" - LT ConditionOperator = "<" - LE ConditionOperator = "<=" - LIKE ConditionOperator = "LIKE" - NOT_LIKE ConditionOperator = "NOT LIKE" - IN ConditionOperator = "IN" - NOT_IN ConditionOperator = "NOT IN" - REGEXP ConditionOperator = "REGEXP" - NOT_REGEXP ConditionOperator = "NOT REGEXP" + IS_NULL ConditionOperator = exp.IsOp + IS_NOT_NULL ConditionOperator = exp.IsNotOp + EQ ConditionOperator = exp.EqOp // = + NE ConditionOperator = exp.NeqOp // != or <> + GT ConditionOperator = exp.GtOp // > + GE ConditionOperator = exp.GteOp // >= + LT ConditionOperator = exp.LtOp // < + LE ConditionOperator = exp.LteOp // <= + LIKE ConditionOperator = exp.LikeOp // LIKE + NOT_LIKE ConditionOperator = exp.NotLikeOp // NOT LIKE + IN ConditionOperator = exp.InOp // IN + NOT_IN ConditionOperator = exp.NotInOp // NOT IN + REGEXP ConditionOperator = exp.RegexpILikeOp // REGEXP + NOT_REGEXP ConditionOperator = exp.RegexpNotILikeOp // NOT REGEXP ) type ExprOption func(option *ConditionExpr) -type TokenValue interface { - GetParam(k string) req.GlobalParams - GetGlobalParamsUser() req.User -} - type ConditionExpr struct { parent *Condition operator ConditionOperator @@ -81,66 +76,43 @@ func (this *ConditionExpr) AppendTo(c *Condition) { } func (this ConditionExpr) ToSql(m TokenValue) db.Expression { - first := "`" + this.fieldResource + "`.`" + this.field + "`" + var first exp.Expression = db.T(this.fieldResource).Col(this.field) if strings.Contains(this.field, "->") { - first = "`" + this.fieldResource + "`." + this.field + "" + first = db.L("?."+this.field+"", db.T(this.fieldResource)) } - value := this.GetTokenSqlValue(m) - - operator := ConditionOperator(strings.ToUpper(string(this.GetOperator()))) - - if value == "" { - // @todo return true - // value = "''" - } - - // - secondary := "" - switch operator { - case IS_NULL: - case IS_NOT_NULL: - - secondary = "" - case EQ, NE, - GT, GE, - LT, LE, - REGEXP, NOT_REGEXP: - - if this.GetTokenType() == SQL { - secondary = value - } else { - secondary = "'" + strings.Trim(value, "'") + "'" - } + var value db.Expression = this.GetTokenSqlValue(m) + switch this.GetOperator() { case LIKE, NOT_LIKE: - secondary = "'%" + strings.Trim(value, "'") + "%'" - case IN, NOT_IN: - secondary = "(" + lo.Ternary(value == "", "''", value) + ")" + value = exp.NewSQLFunctionExpression("CONCAT", "%", value, "%") } - if this.fieldSqlFunc == "json_member_of" { - if this.fieldSqlFuncParam == "" { - return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s, JSON_ARRAY(%s))", secondary, first)) - // return db.Raw(fmt.Sprintf("%s MEMBER OF(%s)", secondary, first)) - } else { - return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s->>'%s', JSON_ARRAY(%s))", secondary, this.fieldSqlFuncParam, first)) - // return db.Raw(fmt.Sprintf("%s MEMBER OF(%s->'%s')", secondary, first, this.FieldSqlFuncParam)) - } - } else if this.fieldSqlFunc == "json_contains" { - if this.fieldSqlFuncParam == "" { - return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s, JSON_ARRAY(%s))", first, secondary)) - } else { - return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s->>'%s', JSON_ARRAY(%s))", first, this.fieldSqlFuncParam, secondary)) - } - } else if this.fieldSqlFunc != "" && this.fieldSqlFuncParam != "" { - first = this.fieldSqlFunc + "(" + first + ", " + this.fieldSqlFuncParam + ")" - } else if this.fieldSqlFunc != "" { - first = this.fieldSqlFunc + "(" + first + ")" + if this.fieldSqlFunc == "" { + return exp.NewBooleanExpression(this.GetOperator(), first, value) } - return db.Raw(strings.Trim(first+" "+string(operator)+" "+secondary, " ")) + switch strings.ToLower(this.fieldSqlFunc) { + case "json_member_of": + if this.fieldSqlFuncParam != "" { + return exp.NewSQLFunctionExpression("JSON_CONTAINS", db.L(this.fieldSqlFuncParam, value), exp.NewSQLFunctionExpression("JSON_ARRAY", first)) + } + return exp.NewSQLFunctionExpression("JSON_CONTAINS", value, exp.NewSQLFunctionExpression("JSON_ARRAY", first)) + case "json_contains": + if this.fieldSqlFuncParam != "" { + return exp.NewSQLFunctionExpression("JSON_CONTAINS", db.L(this.fieldSqlFuncParam, first), exp.NewSQLFunctionExpression("JSON_ARRAY", value)) + } + return exp.NewSQLFunctionExpression("JSON_CONTAINS", first, exp.NewSQLFunctionExpression("JSON_ARRAY", value)) + } + + if this.fieldSqlFuncParam == "" { + first = exp.NewSQLFunctionExpression(this.fieldSqlFunc, first) + } else { + first = exp.NewSQLFunctionExpression(this.fieldSqlFunc, first, db.L(this.fieldSqlFuncParam)) + } + + return exp.NewBooleanExpression(this.GetOperator(), first, value) } func (this ConditionExpr) GetTokenName() string { @@ -151,12 +123,17 @@ func (this ConditionExpr) GetTokenType() TokenType { return this.tokenType } -func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string { +func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) exp.LiteralExpression { if this.GetTokenType() == SQL { - return this.token + return db.L(this.token) } - rv := reflect.ValueOf(this.GetTokenValue(m)) + if this.operator == IS_NULL || this.operator == IS_NOT_NULL { + return db.V(nil) + } + + v := this.GetTokenValue(m) + rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { rv = reflect.Indirect(rv) @@ -164,51 +141,54 @@ func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string { switch rv.Kind() { case reflect.Invalid: - return "" + return db.V("") case reflect.Slice: - aStr := cast.ToStringSlice(rv.Interface()) - - for i := 0; i < len(aStr); i++ { - if aStr[i] != "" { - aStr[i] = "'" + strings.Trim(db.MysqlRealEscapeString(aStr[i]), "'") + "'" - } - } - // 强制使用 in if this.operator == EQ { this.operator = IN } - - return strings.Join(aStr, ", ") + return db.V(v) + case reflect.String: + if this.operator == IN || this.operator == NOT_IN { + parts := strings.Split(rv.String(), ",") + for i, p := range parts { + parts[i] = strings.TrimSpace(p) + } + return db.V(parts) + } case reflect.Struct: - b, _ := json.Marshal(rv.Interface()) - return fmt.Sprintf("'%s'", b) - default: - return db.MysqlRealEscapeString(fmt.Sprintf("%v", rv.Interface())) + b, _ := json.Marshal(v) + return db.V(b) } + return db.V(v) } func (this ConditionExpr) GetTokenValue(m TokenValue) any { switch this.GetTokenType() { case PARAM: + var result valuex.Accessor + if this.matchPrefix != "" { - return m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token)).Value() + result = m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token)) + } else { + result = m.GetParam(this.token) } - return m.GetParam(this.token).Value() + + return result.Any() case STRING: return this.token case FUNC: switch this.token { case "UserID": - return m.GetGlobalParamsUser().ID() + return m.User().ID() case "UserUuid": - return m.GetGlobalParamsUser().Uuid() + return m.User().Uuid() case "UserRolesUuid": - return m.GetGlobalParamsUser().Roles() + return m.User().Roles() case "UserPlatform": - return m.GetGlobalParamsUser().Runtime().Platform() + return m.User().Runtime().Platform() case "UserSaaS": - return m.GetGlobalParamsUser().Runtime().SaaS() + return m.User().Runtime().SaaS() } default: return nil @@ -247,6 +227,12 @@ func Operator(v ConditionOperator) ExprOption { } } +func StringOperator(op string) ExprOption { + return func(option *ConditionExpr) { + option.operator = ToConditionOperator(op) + } +} + func Token(token string, tType TokenType) ExprOption { return func(option *ConditionExpr) { option.token = token @@ -281,3 +267,13 @@ func NewExpr(rResource, rField string, opts ...ExprOption) *ConditionExpr { expr.SetOption(opts...) return expr } + +func GetDBOperator(op string) ConditionOperator { + ops := sqlgen.DefaultDialectOptions().BooleanOperatorLookup + for t, v := range ops { + if string(v) == strings.ToUpper(op) { + return t + } + } + return exp.EqOp +} diff --git a/engine/engine.go b/engine/engine.go index 53f05bc..78ce9ad 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -4,16 +4,21 @@ import ( "encoding/json" "fmt" "reflect" - "strings" + "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.Connection +var conn *db.Database var defaultEngineOptions = engineOptions{ debug: false, @@ -21,76 +26,73 @@ var defaultEngineOptions = engineOptions{ } func init() { - database := db.Open(map[string]db.DBConfig{ - "condition-engine-sqlite3": { - Driver: "sqlite3", - File: ":memory:", - }, + 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[T any] struct { +type Engine struct { code string opts engineOptions - g req.GlobalParams - def func(data T, g req.GlobalParams) error - predicates []*EngineCase[T] + g req.UserAccessor + def func(data reflux.R, g req.UserAccessor) error + predicates []*EngineCase } -func (this Engine[T]) GetCode() string { +func (this Engine) GetCode() string { return this.code } // 公共参数 -func (this *Engine[T]) SetGlobalParams(g req.GlobalParams) *Engine[T] { +func (this *Engine) SetGlobalParams(g req.UserAccessor) *Engine { this.g = g return this } -func (this *Engine[T]) Case(cond *condition.Condition, cb func(data T, g req.GlobalParams) error) *Engine[T] { - this.predicates = append(this.predicates, &EngineCase[T]{cond, cb}) +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[T]) Default(cb func(data T, g req.GlobalParams) error) *Engine[T] { +func (this *Engine) Default(cb func(data reflux.R, g req.UserAccessor) error) *Engine { this.def = cb return this } -func (this Engine[T]) toField(rv reflect.Value) (string, error) { +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) } - - if rv.IsZero() { - return "NULL", nil - } - 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 "", err + return exp.NewSQLFunctionExpression("JSON", nil).As(as), err } - return fmt.Sprintf("CAST('%s' AS JSON1)", b), nil + return exp.NewSQLFunctionExpression("JSON", string(b)).As(as), err case reflect.String: - return fmt.Sprintf("CAST('%s' AS TEXT)", v), nil + 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 fmt.Sprintf("CAST(%d AS INTEGER)", v), nil + return db.Cast(db.V(v), "INTEGER").As(as), nil case reflect.Float64, reflect.Float32: - return fmt.Sprintf("CAST(%v AS REAL)", v), nil + return db.Cast(db.V(v), "REAL").As(as), nil case reflect.Bool: - return fmt.Sprintf("CAST(%v AS BOOL)", v), nil + return db.Cast(db.V(v), "BOOL").As(as), nil } - return "NULL", nil + return db.V(nil).As(as), nil } -func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[string][]string, err error) { +func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][]db.Expression, err error) { rt := rv.Type() if rv.Kind() == reflect.Ptr { @@ -99,11 +101,11 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin } if tables == nil { - tables = map[string][]string{} + tables = map[string][]db.Expression{} } if _, ok := tables[table]; !ok { - tables[table] = []string{} + tables[table] = []db.Expression{} } if rv.Kind() == reflect.Struct { @@ -115,10 +117,10 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin } else { return nil, err } - } else if s, err := this.toField(rv.Field(i)); err != nil { + } 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], fmt.Sprintf("%s as `%s`", s, lo.Ternary(rt.Field(i).Tag.Get("db") != "", rt.Field(i).Tag.Get("db"), rt.Field(i).Name))) + tables[table] = append(tables[table], col) } } } else if rv.Kind() == reflect.Map { @@ -130,10 +132,10 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin } else { return nil, err } - } else if s, err := this.toField(iter.Value()); err != nil { + } else if col, err := this.toField(iter.Value(), iter.Key().String()); err != nil { return nil, err } else { - tables[table] = append(tables[table], fmt.Sprintf("%s as `%s`", s, iter.Key().String())) + tables[table] = append(tables[table], col) } } } else { @@ -143,66 +145,61 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin return tables, nil } -func (this *Engine[T]) Execute(data T) error { +func (this *Engine) Execute(data any) error { + rfx := reflux.New(data) - tables, err := this.toTables(reflect.ValueOf(data), this.GetCode()) + tables, err := this.toTables(rfx.Raw(), this.GetCode()) if err != nil { return err } - columns, ok := tables[this.GetCode()] - - if !ok { - return fmt.Errorf("data is not") - } - - sql := conn.Query().FromSub(fmt.Sprintf("SELECT %s", strings.Join(append(columns, "1 as `_`"), ", ")), this.GetCode()) - - for table, columns := range lo.OmitByKeys(tables, []string{this.GetCode()}) { - sql.JoinSub( - fmt.Sprintf("SELECT %s", strings.Join(append(columns, "1 as `_`"), ", ")), - table, - fmt.Sprintf("%s._", this.GetCode()), - fmt.Sprintf("%s._", table), - ) + 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.NewGlobalParam(`{}`, nil) + this.g = req.NewGlobalParams(`{}`, nil) } - param := &EngineParam{this.g} + param := condition.NewTokenValue(this.g) + cols := []any{} + // 加载条件 for i, p := range this.predicates { - sql.AddSelect(db.Raw(fmt.Sprintf("IFNULL(%s, 0) as `%d`", p.ToSql(param), i))) + cols = append(cols, exp.NewSQLFunctionExpression("IFNULL", p.ToSql(param), 0).As(strconv.Itoa(i))) } - - result := map[string]int64{} + _db = _db.Select(cols...) if this.opts.debug { - fmt.Println("sql", sql.ToSql()) + fmt.Println(_db.ToSQL()) } - if _, err := sql.First(&result); err != nil { - return fmt.Errorf("%s => %s", err, sql.ToSql()) + result, err := _db.Executor().GetRecord() + if err != nil { + sql, _, _ := _db.ToSQL() + return fmt.Errorf("%s => %s", err, sql) } isDefault := true - for k, v := range result { - if v == 0 { + for fnIndex, v := range result { + b := cast.ToInt64(v) + if b == 0 { continue } isDefault = false - if err := this.predicates[cast.ToInt64(k)].Execute(data, this.g); err != nil { - return fmt.Errorf("case %q error, %s", k, err) + 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(data, this.g); err != nil { + if err := this.def(rfx, this.g); err != nil { return fmt.Errorf("case %q error, %s", "default", err) } } @@ -210,16 +207,20 @@ func (this *Engine[T]) Execute(data T) error { return nil } -func New[T any](table string, opt ...EngineOption) *Engine[T] { +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[T]{ + return &Engine{ code: table, opts: opts, - g: req.NewGlobalParam("", nil), + // g: req.NewParam("", nil), } } diff --git a/engine/engine_case.go b/engine/engine_case.go index 5bb29e4..103a40e 100644 --- a/engine/engine_case.go +++ b/engine/engine_case.go @@ -3,22 +3,23 @@ package engine import ( "git.fsdpf.net/go/condition" "git.fsdpf.net/go/db" + "git.fsdpf.net/go/reflux" "git.fsdpf.net/go/req" ) -type EngineCase[T any] struct { +type EngineCase struct { predicate *condition.Condition - cb func(data T, g req.GlobalParams) error + cb func(data reflux.R, g req.UserAccessor) error } -func (this EngineCase[T]) ToSql(param condition.TokenValue) db.Expression { +func (this EngineCase) ToSql(param condition.TokenValue) db.Expression { if this.predicate == nil || this.predicate.IsEmpty() { - return db.Raw("NULL") + return db.V("NULL") } return this.predicate.ToSql(param) } -func (this EngineCase[T]) Execute(data T, g req.GlobalParams) error { +func (this EngineCase) Execute(data reflux.R, g req.UserAccessor) error { return this.cb(data, g) } diff --git a/engine/engine_param.go b/engine/engine_param.go deleted file mode 100644 index 1e083cf..0000000 --- a/engine/engine_param.go +++ /dev/null @@ -1,17 +0,0 @@ -package engine - -import ( - "git.fsdpf.net/go/req" -) - -type EngineParam struct { - data req.GlobalParams -} - -func (this EngineParam) GetParam(k string) req.GlobalParams { - return this.data.Get(k) -} - -func (this EngineParam) GetGlobalParamsUser() req.User { - return this.data.User() -} diff --git a/engine/engine_test.go b/engine/engine_test.go index a24462c..2e1a4f7 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -4,6 +4,7 @@ import ( "testing" "git.fsdpf.net/go/condition" + "git.fsdpf.net/go/reflux" "git.fsdpf.net/go/req" ) @@ -18,26 +19,40 @@ type TestTable struct { } func TestEngine(t *testing.T) { - engine := Engine[any]{ + engine := Engine{ code: "TestTable", - g: req.NewGlobalParam(`{"age": 30}`, nil), + g: req.NewGlobalParams(`{"age": 30}`, nil), } cond1 := condition.New(condition.Describe("条件1")) cond1.SetExpr(condition.NewExpr("TestTable", "age", condition.Token("age", condition.PARAM))) - engine.Case(cond1, func(data any, g req.GlobalParams) error { - t.Log("cond1", data) + engine.Case(cond1, func(data reflux.R, g req.UserAccessor) error { + t.Log("cond1", data.Any()) return nil }) - engine.Default(func(data any, g req.GlobalParams) error { - t.Log("default", data) + engine.Default(func(data reflux.R, g req.UserAccessor) error { + t.Log("default", data.Any()) return nil }) - engine.Execute(map[string]any{ + testData := map[string]any{ + "null": nil, + "age": 30, + "name": "张三", + "map": map[string]any{ + "field": 1, + }, + "bool": false, + "struct": struct{ Name string }{"李四"}, + "struct-ptr": &struct{ Name string }{"张三"}, + "array": []any{1, "a", false}, + } + engine.Execute(testData) + + testData2 := map[string]any{ "null": nil, "age": 30, "name": "张三", @@ -47,15 +62,14 @@ func TestEngine(t *testing.T) { "bool": false, "struct": &struct{ Name string }{"李四"}, "array": []any{1, "a", false}, - }) - - t.Log("execute error", engine.Execute(TestTable{nil, 30, "张三", map[string]any{"field": 1}, false, &struct{ Name string }{"李四"}, []any{1, "a", false}})) + } + t.Log("execute error", engine.Execute(testData2)) } func TestRelationEngine(t *testing.T) { - engine := Engine[any]{ + engine := Engine{ code: "TestTable", - g: req.NewGlobalParam(`{"age": 30}`, nil), + g: req.NewGlobalParams(`{"age": 30}`, nil), opts: engineOptions{ debug: false, relations: []string{"TestTableA"}, @@ -65,8 +79,8 @@ func TestRelationEngine(t *testing.T) { cond1 := condition.New(condition.Describe("条件1")). SetExpr(condition.NewExpr("TestTable", "age", condition.Token("age", condition.PARAM))) - engine.Case(cond1, func(data any, g req.GlobalParams) error { - t.Log("cond1", data) + engine.Case(cond1, func(data reflux.R, g req.UserAccessor) error { + t.Log("cond1", data.Any()) return nil }) @@ -74,17 +88,17 @@ func TestRelationEngine(t *testing.T) { SetExpr(condition.NewExpr("TestTableA", "age", condition.Token("age", condition.PARAM))). SetExpr(condition.NewExpr("TestTable", "age", condition.Token("age", condition.PARAM))) - engine.Case(cond2, func(data any, g req.GlobalParams) error { - t.Log("cond2", data) + engine.Case(cond2, func(data reflux.R, g req.UserAccessor) error { + t.Log("cond2", data.Any()) return nil }) - engine.Default(func(data any, g req.GlobalParams) error { - t.Log("default", data) + engine.Default(func(data reflux.R, g req.UserAccessor) error { + t.Log("default", data.Any()) return nil }) - engine.Execute(map[string]any{ + testData := map[string]any{ "null": nil, "age": 31, "name": "张三", @@ -100,7 +114,8 @@ func TestRelationEngine(t *testing.T) { "age": 22, }, }, - }) + } + engine.Execute(testData) // t.Log("execute error", engine.Execute(TestTable{nil, 30, "张三", map[string]any{"field": 1}, false, &struct{ Name string }{"李四"}, []any{1, "a", false}})) } diff --git a/token_value.go b/token_value.go new file mode 100644 index 0000000..5d3de04 --- /dev/null +++ b/token_value.go @@ -0,0 +1,53 @@ +package condition + +import ( + "git.fsdpf.net/go/reflux/valuex" + "git.fsdpf.net/go/req" +) + +type TokenValue interface { + GetParam(k string) valuex.Accessor + User() req.User +} + +type tValueOpt func(option *tValue) + +func WithUser(user req.User) tValueOpt { + return func(option *tValue) { + option.user = user + } +} + +func NewTokenValue(x valuex.Accessor, opts ...tValueOpt) TokenValue { + tv := &tValue{src: x} + if x == nil { + tv.src = valuex.Nil + } else if up, ok := x.(interface{ User() req.User }); ok { + tv.user = up.User() + } + for _, opt := range opts { + if opt != nil { + opt(tv) + } + } + if tv.user == nil { + panic("user is required") + } + return tv +} + +type tValue struct { + src valuex.Accessor + user req.User +} + +func (this *tValue) GetParam(k string) valuex.Accessor { + if v, ok := this.src.Lookup(k); ok { + return v + } + return valuex.Nil +} + +func (this *tValue) User() req.User { + return this.user +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..291c0b9 --- /dev/null +++ b/util.go @@ -0,0 +1,39 @@ +package condition + +import ( + "strings" +) + +func ToConditionOperator(op string) ConditionOperator { + switch strings.ToUpper(op) { + case "IS NULL": + return IS_NULL + case "IS NOT NULL": + return IS_NOT_NULL + case "=": + return EQ + case "!=", "<>": + return NE + case ">": + return GT + case ">=": + return GE + case "<": + return LT + case "<=": + return LE + case "LIKE": + return LIKE + case "NOT LIKE": + return NOT_LIKE + case "IN": + return IN + case "NOT IN": + return NOT_IN + case "REGEXP": + return REGEXP + case "NOT REGEXP": + return NOT_REGEXP + } + return EQ +}