重构: Condition/Engine 迁移到 db-v2 的表达式树,替换掉旧的 db.Raw 拼字符串

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 时一起解决。
This commit is contained in:
2026-08-21 08:55:21 +08:00
parent 50e1b3630e
commit 5b39565a36
9 changed files with 313 additions and 232 deletions
+1
View File
@@ -21,3 +21,4 @@
# Go workspace file
go.work
.DS_Store
+14 -22
View File
@@ -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...)
}
// 设置条件表达式
+94 -98
View File
@@ -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
}
+71 -70
View File
@@ -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),
}
}
+6 -5
View File
@@ -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)
}
-17
View File
@@ -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()
}
+35 -20
View File
@@ -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}}))
}
+53
View File
@@ -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
}
+39
View File
@@ -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
}