Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcfd2f42da | ||
|
|
d6c3547690 | ||
|
|
a14d0f2c72 | ||
|
|
68cd65f70d | ||
|
|
5b39565a36 | ||
|
|
50e1b3630e | ||
|
|
c3471706f4 | ||
|
|
bb13458114 |
@@ -21,3 +21,4 @@
|
|||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -1,2 +1,54 @@
|
|||||||
# condition
|
# git.fsdpf.net/go/condition(master)
|
||||||
|
|
||||||
|
条件表达式 DSL:把一组条件(等值/比较/模糊匹配/IN 等)组织成可嵌套的 `AND`/`OR` 树,渲染成
|
||||||
|
`db-v2` 的表达式(`db.Expression`),供权限过滤、规则引擎等场景复用同一套条件描述。配合
|
||||||
|
"-v2" 系列项目使用。
|
||||||
|
|
||||||
|
## 这个仓库有什么
|
||||||
|
|
||||||
|
### `Condition`/`ConditionExpr`
|
||||||
|
|
||||||
|
- `Condition`:条件树的节点,`AppendTo`/`SetCondition`/`SetExpr` 组装子条件/表达式,
|
||||||
|
`ToSql(m TokenValue) db.Expression` 渲染成 `db-v2` 的表达式树——内部用
|
||||||
|
`exp.NewExpressionList(this.typ, conditions...)` 构造,不是手工拼 SQL 字符串。
|
||||||
|
`ConditionType` 是 `exp.ExpressionListType`(`AND`/`OR`)的类型别名。
|
||||||
|
- `ConditionExpr`:单个表达式(字段 + 操作符 + 值),`ConditionOperator` 是
|
||||||
|
`exp.BooleanOperation` 的类型别名(`EQ`/`NE`/`GT`/`LIKE`/`IN` 等),跟 `db-v2` 的操作符
|
||||||
|
体系直接对齐,不用自己维护一份字符串常量再转换。
|
||||||
|
- `util.go`:`ToConditionOperator(op string) ConditionOperator` 把字符串(比如从配置/请求
|
||||||
|
参数解析出来的)转成 `ConditionOperator`。
|
||||||
|
|
||||||
|
### `TokenValue`(`token_value.go`)
|
||||||
|
|
||||||
|
条件渲染时取值用的接口:`GetParam(k string) valuex.Accessor`(配合 `reflux/valuex` 统一属性
|
||||||
|
访问方式)+ `User() req.User`。`NewTokenValue(x valuex.Accessor, opts ...tValueOpt)` 构造,
|
||||||
|
`WithUser(user)` 显式指定用户;不传时会尝试从 `x` 上探测 `User()` 方法,两者都拿不到会
|
||||||
|
panic——`TokenValue` 必须绑定一个具体用户,不支持匿名/无用户场景。
|
||||||
|
|
||||||
|
### JSON 序列化(`condition_json.go`)
|
||||||
|
|
||||||
|
`Condition`/`ConditionExpr` 都实现了 `MarshalJSON`/`UnmarshalJSON`(内部过一层 DTO 结构避免
|
||||||
|
递归),`FromMap(m map[string]any) (*Condition, error)` 直接从已解析好的 map 构造,不用先转
|
||||||
|
成 JSON 字符串再解析一遍——典型场景是权限配置、规则条件存在数据库的 JSON 列里,读出来直接
|
||||||
|
反序列化成 `Condition` 用。
|
||||||
|
|
||||||
|
### `engine` 子包:规则引擎
|
||||||
|
|
||||||
|
`Engine[T any]`:按 `Case(cond *condition.Condition, cb func(data T, g req.GlobalParams) error)`
|
||||||
|
注册一组"条件 -> 回调"规则,`Execute(data T)` 对传入数据求值,命中第一个条件为真的分支就
|
||||||
|
调用对应回调(`Default(cb)` 兜底)。内部通过 `db/engine`(sqlite3 内存库)把 `data`(用
|
||||||
|
`reflux.R` 包装)当成一行虚拟数据跑 SQL 判断条件是否成立,不用自己写一套表达式求值器。
|
||||||
|
`EngineOption`(`Debug()`/`Relation(...)`)控制调试日志和关联字段展开。
|
||||||
|
|
||||||
|
## 已知问题
|
||||||
|
|
||||||
|
`engine.go` 在调用方没有显式传 `GlobalParams` 时,会用 `nil` user 构造一个兜底值,这跟
|
||||||
|
`TokenValue` 现在强制要求非 nil user 冲突,`TestEngine` 目前会 panic——待 `Engine.Execute`
|
||||||
|
支持显式传 user 时一并解决,暂时未修。
|
||||||
|
|
||||||
|
## 分支状态
|
||||||
|
|
||||||
|
`master` 是持续开发中的新架构,**跟老仓库 `v1-legacy` 分支不兼容**——`Condition.ToSql` 从手工
|
||||||
|
拼 SQL 字符串换成了 `db-v2` 表达式树,`TokenValue` 从 `req.GlobalParams` 换成了
|
||||||
|
`reflux/valuex`,`engine` 包的连接管理也换成了 `db/engine`。还在用老版 API 的老项目,参见
|
||||||
|
`v1-legacy` 分支。
|
||||||
|
|||||||
+14
-22
@@ -5,15 +5,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.fsdpf.net/go/db"
|
"git.fsdpf.net/go/db"
|
||||||
|
"git.fsdpf.net/go/db/exp"
|
||||||
"git.fsdpf.net/go/utils"
|
"git.fsdpf.net/go/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConditionType string
|
type ConditionType = exp.ExpressionListType
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OR ConditionType = "OR"
|
AND ConditionType = exp.AndType
|
||||||
AND ConditionType = "AND"
|
OR ConditionType = exp.OrType
|
||||||
)
|
)
|
||||||
|
|
||||||
type Condition struct {
|
type Condition struct {
|
||||||
@@ -92,44 +93,35 @@ func (this Condition) IsAlwaysRight() bool {
|
|||||||
return !lo.Some(flag, []bool{false})
|
return !lo.Some(flag, []bool{false})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 SQL 语句
|
// ToSql 生成 SQL 语句
|
||||||
func (this Condition) ToSql(m TokenValue) db.Expression {
|
func (this Condition) ToSql(m TokenValue) db.Expression {
|
||||||
conditions := []string{}
|
conditions := []exp.Expression{}
|
||||||
// 表达式
|
// 表达式
|
||||||
for _, item := range this.exprs {
|
for _, item := range this.exprs {
|
||||||
if item.IsIgnoreEmptyParma(m) {
|
if item.IsIgnoreEmptyParma(m) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conditions = append(conditions, string(item.ToSql(m)))
|
conditions = append(conditions, item.ToSql(m))
|
||||||
}
|
}
|
||||||
// 条件子集
|
// 条件子集
|
||||||
for _, item := range this.childrens {
|
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 {
|
if this.parent == nil && this.Type() == OR {
|
||||||
return db.Raw("false")
|
return db.V(false)
|
||||||
} else if this.parent == nil && this.Type() == AND {
|
} else if this.parent == nil && this.Type() == AND {
|
||||||
return db.Raw("true")
|
return db.V(true)
|
||||||
}
|
}
|
||||||
return db.Raw("")
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
// 包裹 SQL, 避免语法表达错误
|
|
||||||
if this.parent == nil || this.Type() == OR {
|
|
||||||
sql = "(" + sql + ")"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.describe != "" {
|
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
@@ -7,13 +7,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.fsdpf.net/go/db"
|
"git.fsdpf.net/go/db"
|
||||||
"git.fsdpf.net/go/req"
|
"git.fsdpf.net/go/db/exp"
|
||||||
"github.com/samber/lo"
|
"git.fsdpf.net/go/db/sqlgen"
|
||||||
"github.com/spf13/cast"
|
"git.fsdpf.net/go/reflux/valuex"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenType string
|
type TokenType string
|
||||||
type ConditionOperator string
|
type ConditionOperator = exp.BooleanOperation
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SQL TokenType = "sql"
|
SQL TokenType = "sql"
|
||||||
@@ -23,29 +23,24 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IS_NULL ConditionOperator = "IS NULL"
|
IS_NULL ConditionOperator = exp.IsOp
|
||||||
IS_NOT_NULL ConditionOperator = "IS NOT NULL"
|
IS_NOT_NULL ConditionOperator = exp.IsNotOp
|
||||||
EQ ConditionOperator = "="
|
EQ ConditionOperator = exp.EqOp // =
|
||||||
NE ConditionOperator = "!="
|
NE ConditionOperator = exp.NeqOp // != or <>
|
||||||
GT ConditionOperator = ">"
|
GT ConditionOperator = exp.GtOp // >
|
||||||
GE ConditionOperator = ">="
|
GE ConditionOperator = exp.GteOp // >=
|
||||||
LT ConditionOperator = "<"
|
LT ConditionOperator = exp.LtOp // <
|
||||||
LE ConditionOperator = "<="
|
LE ConditionOperator = exp.LteOp // <=
|
||||||
LIKE ConditionOperator = "LIKE"
|
LIKE ConditionOperator = exp.LikeOp // LIKE
|
||||||
NOT_LIKE ConditionOperator = "NOT LIKE"
|
NOT_LIKE ConditionOperator = exp.NotLikeOp // NOT LIKE
|
||||||
IN ConditionOperator = "IN"
|
IN ConditionOperator = exp.InOp // IN
|
||||||
NOT_IN ConditionOperator = "NOT IN"
|
NOT_IN ConditionOperator = exp.NotInOp // NOT IN
|
||||||
REGEXP ConditionOperator = "REGEXP"
|
REGEXP ConditionOperator = exp.RegexpILikeOp // REGEXP
|
||||||
NOT_REGEXP ConditionOperator = "NOT REGEXP"
|
NOT_REGEXP ConditionOperator = exp.RegexpNotILikeOp // NOT REGEXP
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExprOption func(option *ConditionExpr)
|
type ExprOption func(option *ConditionExpr)
|
||||||
|
|
||||||
type TokenValue interface {
|
|
||||||
GetParam(k string) req.GlobalParams
|
|
||||||
GetGlobalParamsUser() req.User
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConditionExpr struct {
|
type ConditionExpr struct {
|
||||||
parent *Condition
|
parent *Condition
|
||||||
operator ConditionOperator
|
operator ConditionOperator
|
||||||
@@ -81,66 +76,43 @@ func (this *ConditionExpr) AppendTo(c *Condition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this ConditionExpr) ToSql(m TokenValue) db.Expression {
|
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, "->") {
|
if strings.Contains(this.field, "->") {
|
||||||
first = "`" + this.fieldResource + "`." + this.field + ""
|
first = db.L("?."+this.field+"", db.T(this.fieldResource))
|
||||||
}
|
}
|
||||||
|
|
||||||
value := this.GetTokenSqlValue(m)
|
var value db.Expression = 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, "'") + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
switch this.GetOperator() {
|
||||||
case LIKE, NOT_LIKE:
|
case LIKE, NOT_LIKE:
|
||||||
secondary = "'%" + strings.Trim(value, "'") + "%'"
|
value = exp.NewSQLFunctionExpression("CONCAT", "%", value, "%")
|
||||||
case IN, NOT_IN:
|
|
||||||
secondary = "(" + lo.Ternary(value == "", "''", value) + ")"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.fieldSqlFunc == "json_member_of" {
|
if this.fieldSqlFunc == "" {
|
||||||
if this.fieldSqlFuncParam == "" {
|
return exp.NewBooleanExpression(this.GetOperator(), first, value)
|
||||||
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 + ")"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func (this ConditionExpr) GetTokenName() string {
|
||||||
@@ -151,12 +123,17 @@ func (this ConditionExpr) GetTokenType() TokenType {
|
|||||||
return this.tokenType
|
return this.tokenType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string {
|
func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) exp.LiteralExpression {
|
||||||
if this.GetTokenType() == SQL {
|
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 {
|
if rv.Kind() == reflect.Ptr {
|
||||||
rv = reflect.Indirect(rv)
|
rv = reflect.Indirect(rv)
|
||||||
@@ -164,51 +141,54 @@ func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string {
|
|||||||
|
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Invalid:
|
case reflect.Invalid:
|
||||||
return ""
|
return db.V("")
|
||||||
case reflect.Slice:
|
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
|
// 强制使用 in
|
||||||
if this.operator == EQ {
|
if this.operator == EQ {
|
||||||
this.operator = IN
|
this.operator = IN
|
||||||
}
|
}
|
||||||
|
return db.V(v)
|
||||||
return strings.Join(aStr, ", ")
|
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:
|
case reflect.Struct:
|
||||||
b, _ := json.Marshal(rv.Interface())
|
b, _ := json.Marshal(v)
|
||||||
return fmt.Sprintf("'%s'", b)
|
return db.V(b)
|
||||||
default:
|
|
||||||
return db.MysqlRealEscapeString(fmt.Sprintf("%v", rv.Interface()))
|
|
||||||
}
|
}
|
||||||
|
return db.V(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this ConditionExpr) GetTokenValue(m TokenValue) any {
|
func (this ConditionExpr) GetTokenValue(m TokenValue) any {
|
||||||
switch this.GetTokenType() {
|
switch this.GetTokenType() {
|
||||||
case PARAM:
|
case PARAM:
|
||||||
|
var result valuex.Accessor
|
||||||
|
|
||||||
if this.matchPrefix != "" {
|
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:
|
case STRING:
|
||||||
return this.token
|
return this.token
|
||||||
case FUNC:
|
case FUNC:
|
||||||
switch this.token {
|
switch this.token {
|
||||||
case "UserID":
|
case "UserID":
|
||||||
return m.GetGlobalParamsUser().ID()
|
return m.User().ID()
|
||||||
case "UserUuid":
|
case "UserUuid":
|
||||||
return m.GetGlobalParamsUser().Uuid()
|
return m.User().Uuid()
|
||||||
case "UserRolesUuid":
|
case "UserRolesUuid":
|
||||||
return m.GetGlobalParamsUser().Roles()
|
return m.User().Roles()
|
||||||
case "UserPlatform":
|
case "UserPlatform":
|
||||||
return m.GetGlobalParamsUser().Runtime().Platform()
|
return m.User().Runtime().Platform()
|
||||||
case "UserSaaS":
|
case "UserSaaS":
|
||||||
return m.GetGlobalParamsUser().Runtime().SaaS()
|
return m.User().Runtime().SaaS()
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil
|
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 {
|
func Token(token string, tType TokenType) ExprOption {
|
||||||
return func(option *ConditionExpr) {
|
return func(option *ConditionExpr) {
|
||||||
option.token = token
|
option.token = token
|
||||||
@@ -281,3 +267,13 @@ func NewExpr(rResource, rField string, opts ...ExprOption) *ConditionExpr {
|
|||||||
expr.SetOption(opts...)
|
expr.SetOption(opts...)
|
||||||
return expr
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package condition
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// conditionDTO 是 Condition 的 JSON 中间结构,用于避免 UnmarshalJSON 递归
|
||||||
|
type conditionDTO struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Describe string `json:"describe,omitempty"`
|
||||||
|
Exprs []*exprDTO `json:"exprs,omitempty"`
|
||||||
|
Childrens []*conditionDTO `json:"childrens,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type exprDTO struct {
|
||||||
|
Operator string `json:"operator"`
|
||||||
|
Field string `json:"field"`
|
||||||
|
FieldResource string `json:"fieldResource"`
|
||||||
|
FieldSqlFunc string `json:"fieldSqlFunc,omitempty"`
|
||||||
|
FieldSqlFuncParam string `json:"fieldSqlFuncParam,omitempty"`
|
||||||
|
IgnoreEmptyParam bool `json:"ignoreEmptyParam,omitempty"`
|
||||||
|
TokenType TokenType `json:"tokenType"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Condition) UnmarshalJSON(data []byte) error {
|
||||||
|
var dto conditionDTO
|
||||||
|
if err := json.Unmarshal(data, &dto); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conditionFromDTO(this, &dto)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Condition) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(conditionToDTO(&this))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ConditionExpr) UnmarshalJSON(data []byte) error {
|
||||||
|
var dto exprDTO
|
||||||
|
if err := json.Unmarshal(data, &dto); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*this = *exprFromDTO(&dto)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this ConditionExpr) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(exprToDTO(&this))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMap 将 map[string]any 转换为 *Condition
|
||||||
|
func FromMap(m map[string]any) (*Condition, error) {
|
||||||
|
data, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cond := New()
|
||||||
|
if err := json.Unmarshal(data, cond); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cond, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionFromDTO(c *Condition, dto *conditionDTO) {
|
||||||
|
if strings.EqualFold(dto.Type, "OR") {
|
||||||
|
c.typ = OR
|
||||||
|
} else {
|
||||||
|
c.typ = AND
|
||||||
|
}
|
||||||
|
c.describe = dto.Describe
|
||||||
|
for _, e := range dto.Exprs {
|
||||||
|
c.SetExpr(exprFromDTO(e))
|
||||||
|
}
|
||||||
|
for _, child := range dto.Childrens {
|
||||||
|
childCond := &Condition{}
|
||||||
|
conditionFromDTO(childCond, child)
|
||||||
|
c.SetCondition(childCond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionToDTO(c *Condition) *conditionDTO {
|
||||||
|
typ := "AND"
|
||||||
|
if c.typ == OR {
|
||||||
|
typ = "OR"
|
||||||
|
}
|
||||||
|
dto := &conditionDTO{Type: typ, Describe: c.describe}
|
||||||
|
for _, e := range c.exprs {
|
||||||
|
dto.Exprs = append(dto.Exprs, exprToDTO(e))
|
||||||
|
}
|
||||||
|
for _, child := range c.childrens {
|
||||||
|
dto.Childrens = append(dto.Childrens, conditionToDTO(child))
|
||||||
|
}
|
||||||
|
return dto
|
||||||
|
}
|
||||||
|
|
||||||
|
func exprFromDTO(dto *exprDTO) *ConditionExpr {
|
||||||
|
tokenType := dto.TokenType
|
||||||
|
if tokenType == "" {
|
||||||
|
tokenType = STRING
|
||||||
|
}
|
||||||
|
return &ConditionExpr{
|
||||||
|
operator: ToConditionOperator(dto.Operator),
|
||||||
|
field: dto.Field,
|
||||||
|
fieldResource: dto.FieldResource,
|
||||||
|
fieldSqlFunc: dto.FieldSqlFunc,
|
||||||
|
fieldSqlFuncParam: dto.FieldSqlFuncParam,
|
||||||
|
ignoreEmptyParma: dto.IgnoreEmptyParam,
|
||||||
|
tokenType: tokenType,
|
||||||
|
token: dto.Token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func exprToDTO(e *ConditionExpr) *exprDTO {
|
||||||
|
return &exprDTO{
|
||||||
|
Operator: operatorToString(e.operator),
|
||||||
|
Field: e.field,
|
||||||
|
FieldResource: e.fieldResource,
|
||||||
|
FieldSqlFunc: e.fieldSqlFunc,
|
||||||
|
FieldSqlFuncParam: e.fieldSqlFuncParam,
|
||||||
|
IgnoreEmptyParam: e.ignoreEmptyParma,
|
||||||
|
TokenType: e.tokenType,
|
||||||
|
Token: e.token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func operatorToString(op ConditionOperator) string {
|
||||||
|
switch op {
|
||||||
|
case IS_NULL:
|
||||||
|
return "IS NULL"
|
||||||
|
case IS_NOT_NULL:
|
||||||
|
return "IS NOT NULL"
|
||||||
|
case EQ:
|
||||||
|
return "="
|
||||||
|
case NE:
|
||||||
|
return "!="
|
||||||
|
case GT:
|
||||||
|
return ">"
|
||||||
|
case GE:
|
||||||
|
return ">="
|
||||||
|
case LT:
|
||||||
|
return "<"
|
||||||
|
case LE:
|
||||||
|
return "<="
|
||||||
|
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"
|
||||||
|
default:
|
||||||
|
return "="
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package condition_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.fsdpf.net/go/condition"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type conditionJSONTest struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConditionJSONSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(conditionJSONTest))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造一个有代表性的多层条件
|
||||||
|
func buildTestCondition() *condition.Condition {
|
||||||
|
cond := condition.New(condition.Describe("测试"))
|
||||||
|
cond.SetExpr(condition.NewExpr("User", "name",
|
||||||
|
condition.Operator(condition.LIKE),
|
||||||
|
condition.Token("张三", condition.STRING),
|
||||||
|
))
|
||||||
|
cond.SetExpr(condition.NewExpr("User", "age",
|
||||||
|
condition.Operator(condition.GE),
|
||||||
|
condition.Token("18", condition.STRING),
|
||||||
|
))
|
||||||
|
|
||||||
|
sub := condition.New(condition.Type(condition.OR))
|
||||||
|
sub.SetExpr(condition.NewExpr("User", "status",
|
||||||
|
condition.Operator(condition.IN),
|
||||||
|
condition.Token("active", condition.STRING),
|
||||||
|
))
|
||||||
|
sub.SetExpr(condition.NewExpr("User", "roles",
|
||||||
|
condition.FieldSqlFn("json_contains", ""),
|
||||||
|
condition.Token("admin", condition.STRING),
|
||||||
|
))
|
||||||
|
cond.SetCondition(sub)
|
||||||
|
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *conditionJSONTest) TestMarshalUnmarshal() {
|
||||||
|
orig := buildTestCondition()
|
||||||
|
|
||||||
|
data, err := json.Marshal(orig)
|
||||||
|
t.Require().NoError(err)
|
||||||
|
t.T().Log("JSON:", string(data))
|
||||||
|
|
||||||
|
got := condition.New()
|
||||||
|
t.Require().NoError(json.Unmarshal(data, got))
|
||||||
|
|
||||||
|
// 二次序列化结果应一致
|
||||||
|
data2, err := json.Marshal(got)
|
||||||
|
t.Require().NoError(err)
|
||||||
|
t.Equal(string(data), string(data2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *conditionJSONTest) TestFromMap() {
|
||||||
|
m := map[string]any{
|
||||||
|
"type": "AND",
|
||||||
|
"describe": "来自Map",
|
||||||
|
"exprs": []any{
|
||||||
|
map[string]any{
|
||||||
|
"operator": "=",
|
||||||
|
"field": "name",
|
||||||
|
"fieldResource": "User",
|
||||||
|
"tokenType": "string",
|
||||||
|
"token": "张三",
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"operator": ">=",
|
||||||
|
"field": "age",
|
||||||
|
"fieldResource": "User",
|
||||||
|
"tokenType": "string",
|
||||||
|
"token": "18",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"childrens": []any{
|
||||||
|
map[string]any{
|
||||||
|
"type": "OR",
|
||||||
|
"exprs": []any{
|
||||||
|
map[string]any{
|
||||||
|
"operator": "IS NULL",
|
||||||
|
"field": "deleted_at",
|
||||||
|
"fieldResource": "User",
|
||||||
|
"tokenType": "string",
|
||||||
|
"token": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cond, err := condition.FromMap(m)
|
||||||
|
t.Require().NoError(err)
|
||||||
|
t.False(cond.IsEmpty())
|
||||||
|
|
||||||
|
data, err := json.Marshal(cond)
|
||||||
|
t.Require().NoError(err)
|
||||||
|
t.T().Log("FromMap JSON:", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *conditionJSONTest) TestRoundTrip() {
|
||||||
|
orig := buildTestCondition()
|
||||||
|
|
||||||
|
// Condition → JSON → Condition → JSON,两次 JSON 应一致
|
||||||
|
data1, err := json.Marshal(orig)
|
||||||
|
t.Require().NoError(err)
|
||||||
|
|
||||||
|
cond2 := condition.New()
|
||||||
|
t.Require().NoError(json.Unmarshal(data1, cond2))
|
||||||
|
|
||||||
|
data2, err := json.Marshal(cond2)
|
||||||
|
t.Require().NoError(err)
|
||||||
|
|
||||||
|
t.JSONEq(string(data1), string(data2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *conditionJSONTest) TestFromMapEmpty() {
|
||||||
|
cond, err := condition.FromMap(map[string]any{})
|
||||||
|
t.Require().NoError(err)
|
||||||
|
t.True(cond.IsEmpty())
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package condition_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.fsdpf.net/go/condition"
|
||||||
|
"git.fsdpf.net/go/db"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
_ "git.fsdpf.net/go/db/dialect/sqlite3"
|
||||||
|
db_eng "git.fsdpf.net/go/db/engine"
|
||||||
|
"git.fsdpf.net/go/db/sqlgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
type conditionTest struct {
|
||||||
|
suite.Suite
|
||||||
|
db *db.Database
|
||||||
|
esg sqlgen.ExpressionSQLGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConditionSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(conditionTest))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *conditionTest) SetupSuite() {
|
||||||
|
database := db_eng.Open(map[string]db_eng.DBConfig{
|
||||||
|
"condition-test": db_eng.NewDBConfig("sqlite3", db_eng.WithSQLiteFile(":memory:")),
|
||||||
|
})
|
||||||
|
t.db = database.Connection("condition-test")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *conditionTest) TestToSql() {
|
||||||
|
cond := condition.New(condition.Describe("测试"))
|
||||||
|
cond.SetExpr(condition.NewExpr("User", "name", condition.Operator(condition.LIKE), condition.Token("张三", condition.STRING))).
|
||||||
|
SetExpr(condition.NewExpr("User", "age", condition.Token("18", condition.STRING))).
|
||||||
|
SetExpr(condition.NewExpr("User", "attr->'birthday'", condition.Token("18", condition.STRING))).
|
||||||
|
SetExpr(condition.NewExpr("User", "roles", condition.FieldSqlFn("json_contains", ""), condition.Token("admin", condition.STRING)))
|
||||||
|
// SetExpr(condition.NewExpr("User", "roles", condition.FieldSqlFn("json_contains", ""), condition.Token("admin", condition.STRING)))
|
||||||
|
|
||||||
|
t.T().Log(t.db.From(db.T("users").As("User")).Where(cond.ToSql(nil)).ToSQL())
|
||||||
|
}
|
||||||
+71
-70
@@ -4,16 +4,21 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strconv"
|
||||||
|
|
||||||
"git.fsdpf.net/go/condition"
|
"git.fsdpf.net/go/condition"
|
||||||
"git.fsdpf.net/go/db"
|
"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"
|
"git.fsdpf.net/go/req"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
var conn *db.Connection
|
var conn *db.Database
|
||||||
|
|
||||||
var defaultEngineOptions = engineOptions{
|
var defaultEngineOptions = engineOptions{
|
||||||
debug: false,
|
debug: false,
|
||||||
@@ -21,76 +26,73 @@ var defaultEngineOptions = engineOptions{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
database := db.Open(map[string]db.DBConfig{
|
database := db_eng.Open(map[string]db_eng.DBConfig{
|
||||||
"condition-engine-sqlite3": {
|
"condition-engine-sqlite3": db_eng.NewDBConfig("sqlite3", db_eng.WithSQLiteFile(":memory:")),
|
||||||
Driver: "sqlite3",
|
|
||||||
File: ":memory:",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
conn = database.Connection("condition-engine-sqlite3")
|
conn = database.Connection("condition-engine-sqlite3")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Engine[T any] struct {
|
type Engine struct {
|
||||||
code string
|
code string
|
||||||
opts engineOptions
|
opts engineOptions
|
||||||
g req.GlobalParams
|
g req.UserAccessor
|
||||||
def func(data T, g req.GlobalParams) error
|
def func(data reflux.R, g req.UserAccessor) error
|
||||||
predicates []*EngineCase[T]
|
predicates []*EngineCase
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this Engine[T]) GetCode() string {
|
func (this Engine) GetCode() string {
|
||||||
return this.code
|
return this.code
|
||||||
}
|
}
|
||||||
|
|
||||||
// 公共参数
|
// 公共参数
|
||||||
func (this *Engine[T]) SetGlobalParams(g req.GlobalParams) *Engine[T] {
|
func (this *Engine) SetGlobalParams(g req.UserAccessor) *Engine {
|
||||||
this.g = g
|
this.g = g
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Engine[T]) Case(cond *condition.Condition, cb func(data T, g req.GlobalParams) error) *Engine[T] {
|
func (this *Engine) Case(cond *condition.Condition, cb func(data reflux.R, g req.UserAccessor) error) *Engine {
|
||||||
this.predicates = append(this.predicates, &EngineCase[T]{cond, cb})
|
this.predicates = append(this.predicates, &EngineCase{cond, cb})
|
||||||
return this
|
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
|
this.def = cb
|
||||||
return this
|
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 {
|
if rv.Kind() == reflect.Ptr {
|
||||||
rv = reflect.Indirect(rv)
|
rv = reflect.Indirect(rv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv.IsZero() {
|
|
||||||
return "NULL", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v := rv.Interface()
|
v := rv.Interface()
|
||||||
|
|
||||||
switch reflect.TypeOf(v).Kind() {
|
switch reflect.TypeOf(v).Kind() {
|
||||||
case reflect.Map, reflect.Array, reflect.Slice, reflect.Struct:
|
case reflect.Map, reflect.Array, reflect.Slice, reflect.Struct:
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
if err != nil {
|
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:
|
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,
|
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int8, reflect.Int16,
|
||||||
reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint8, reflect.Uint16:
|
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:
|
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:
|
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()
|
rt := rv.Type()
|
||||||
|
|
||||||
if rv.Kind() == reflect.Ptr {
|
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 {
|
if tables == nil {
|
||||||
tables = map[string][]string{}
|
tables = map[string][]db.Expression{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := tables[table]; !ok {
|
if _, ok := tables[table]; !ok {
|
||||||
tables[table] = []string{}
|
tables[table] = []db.Expression{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rv.Kind() == reflect.Struct {
|
if rv.Kind() == reflect.Struct {
|
||||||
@@ -115,10 +117,10 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin
|
|||||||
} else {
|
} else {
|
||||||
return nil, err
|
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
|
return nil, err
|
||||||
} else {
|
} 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 {
|
} else if rv.Kind() == reflect.Map {
|
||||||
@@ -130,10 +132,10 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin
|
|||||||
} else {
|
} else {
|
||||||
return nil, err
|
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
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
tables[table] = append(tables[table], fmt.Sprintf("%s as `%s`", s, iter.Key().String()))
|
tables[table] = append(tables[table], col)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -143,66 +145,61 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin
|
|||||||
return tables, nil
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
columns, ok := tables[this.GetCode()]
|
ts := []any{}
|
||||||
|
// 加载数据
|
||||||
if !ok {
|
for table, columns := range tables {
|
||||||
return fmt.Errorf("data is not")
|
ts = append(ts, db.Select(this.toSelectCols(columns)...).As(table))
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
_db := conn.From(ts...)
|
||||||
|
|
||||||
if this.g == nil {
|
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 {
|
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)))
|
||||||
}
|
}
|
||||||
|
_db = _db.Select(cols...)
|
||||||
result := map[string]int64{}
|
|
||||||
|
|
||||||
if this.opts.debug {
|
if this.opts.debug {
|
||||||
fmt.Println("sql", sql.ToSql())
|
fmt.Println(_db.ToSQL())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sql.First(&result); err != nil {
|
result, err := _db.Executor().GetRecord()
|
||||||
return fmt.Errorf("%s => %s", err, sql.ToSql())
|
if err != nil {
|
||||||
|
sql, _, _ := _db.ToSQL()
|
||||||
|
return fmt.Errorf("%s => %s", err, sql)
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefault := true
|
isDefault := true
|
||||||
for k, v := range result {
|
for fnIndex, v := range result {
|
||||||
if v == 0 {
|
b := cast.ToInt64(v)
|
||||||
|
if b == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefault = false
|
isDefault = false
|
||||||
|
|
||||||
if err := this.predicates[cast.ToInt64(k)].Execute(data, this.g); err != nil {
|
if err := this.predicates[cast.ToInt64(fnIndex)].Execute(rfx, this.g); err != nil {
|
||||||
return fmt.Errorf("case %q error, %s", k, err)
|
return fmt.Errorf("case %q error, %s", fnIndex, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDefault && this.def != nil {
|
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)
|
return fmt.Errorf("case %q error, %s", "default", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,16 +207,20 @@ func (this *Engine[T]) Execute(data T) error {
|
|||||||
return nil
|
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
|
opts := defaultEngineOptions
|
||||||
|
|
||||||
for _, o := range opt {
|
for _, o := range opt {
|
||||||
o.apply(&opts)
|
o.apply(&opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Engine[T]{
|
return &Engine{
|
||||||
code: table,
|
code: table,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
g: req.NewGlobalParam("", nil),
|
// g: req.NewParam("", nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,23 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"git.fsdpf.net/go/condition"
|
"git.fsdpf.net/go/condition"
|
||||||
"git.fsdpf.net/go/db"
|
"git.fsdpf.net/go/db"
|
||||||
|
"git.fsdpf.net/go/reflux"
|
||||||
"git.fsdpf.net/go/req"
|
"git.fsdpf.net/go/req"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EngineCase[T any] struct {
|
type EngineCase struct {
|
||||||
predicate *condition.Condition
|
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() {
|
if this.predicate == nil || this.predicate.IsEmpty() {
|
||||||
return db.Raw("NULL")
|
return db.V("NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.predicate.ToSql(param)
|
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)
|
return this.cb(data, g)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.fsdpf.net/go/condition"
|
"git.fsdpf.net/go/condition"
|
||||||
|
"git.fsdpf.net/go/reflux"
|
||||||
"git.fsdpf.net/go/req"
|
"git.fsdpf.net/go/req"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,26 +19,40 @@ type TestTable struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEngine(t *testing.T) {
|
func TestEngine(t *testing.T) {
|
||||||
engine := Engine[any]{
|
engine := Engine{
|
||||||
code: "TestTable",
|
code: "TestTable",
|
||||||
g: req.NewGlobalParam(`{"age": 30}`, nil),
|
g: req.NewGlobalParams(`{"age": 30}`, nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
cond1 := condition.New(condition.Describe("条件1"))
|
cond1 := condition.New(condition.Describe("条件1"))
|
||||||
|
|
||||||
cond1.SetExpr(condition.NewExpr("TestTable", "age", condition.Token("age", condition.PARAM)))
|
cond1.SetExpr(condition.NewExpr("TestTable", "age", condition.Token("age", condition.PARAM)))
|
||||||
|
|
||||||
engine.Case(cond1, func(data any, g req.GlobalParams) error {
|
engine.Case(cond1, func(data reflux.R, g req.UserAccessor) error {
|
||||||
t.Log("cond1", data)
|
t.Log("cond1", data.Any())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
engine.Default(func(data any, g req.GlobalParams) error {
|
engine.Default(func(data reflux.R, g req.UserAccessor) error {
|
||||||
t.Log("default", data)
|
t.Log("default", data.Any())
|
||||||
return nil
|
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,
|
"null": nil,
|
||||||
"age": 30,
|
"age": 30,
|
||||||
"name": "张三",
|
"name": "张三",
|
||||||
@@ -47,15 +62,14 @@ func TestEngine(t *testing.T) {
|
|||||||
"bool": false,
|
"bool": false,
|
||||||
"struct": &struct{ Name string }{"李四"},
|
"struct": &struct{ Name string }{"李四"},
|
||||||
"array": []any{1, "a", false},
|
"array": []any{1, "a", false},
|
||||||
})
|
}
|
||||||
|
t.Log("execute error", engine.Execute(testData2))
|
||||||
t.Log("execute error", engine.Execute(TestTable{nil, 30, "张三", map[string]any{"field": 1}, false, &struct{ Name string }{"李四"}, []any{1, "a", false}}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRelationEngine(t *testing.T) {
|
func TestRelationEngine(t *testing.T) {
|
||||||
engine := Engine[any]{
|
engine := Engine{
|
||||||
code: "TestTable",
|
code: "TestTable",
|
||||||
g: req.NewGlobalParam(`{"age": 30}`, nil),
|
g: req.NewGlobalParams(`{"age": 30}`, nil),
|
||||||
opts: engineOptions{
|
opts: engineOptions{
|
||||||
debug: false,
|
debug: false,
|
||||||
relations: []string{"TestTableA"},
|
relations: []string{"TestTableA"},
|
||||||
@@ -65,8 +79,8 @@ func TestRelationEngine(t *testing.T) {
|
|||||||
cond1 := condition.New(condition.Describe("条件1")).
|
cond1 := condition.New(condition.Describe("条件1")).
|
||||||
SetExpr(condition.NewExpr("TestTable", "age", condition.Token("age", condition.PARAM)))
|
SetExpr(condition.NewExpr("TestTable", "age", condition.Token("age", condition.PARAM)))
|
||||||
|
|
||||||
engine.Case(cond1, func(data any, g req.GlobalParams) error {
|
engine.Case(cond1, func(data reflux.R, g req.UserAccessor) error {
|
||||||
t.Log("cond1", data)
|
t.Log("cond1", data.Any())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -74,17 +88,17 @@ func TestRelationEngine(t *testing.T) {
|
|||||||
SetExpr(condition.NewExpr("TestTableA", "age", condition.Token("age", condition.PARAM))).
|
SetExpr(condition.NewExpr("TestTableA", "age", condition.Token("age", condition.PARAM))).
|
||||||
SetExpr(condition.NewExpr("TestTable", "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 {
|
engine.Case(cond2, func(data reflux.R, g req.UserAccessor) error {
|
||||||
t.Log("cond2", data)
|
t.Log("cond2", data.Any())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
engine.Default(func(data any, g req.GlobalParams) error {
|
engine.Default(func(data reflux.R, g req.UserAccessor) error {
|
||||||
t.Log("default", data)
|
t.Log("default", data.Any())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
engine.Execute(map[string]any{
|
testData := map[string]any{
|
||||||
"null": nil,
|
"null": nil,
|
||||||
"age": 31,
|
"age": 31,
|
||||||
"name": "张三",
|
"name": "张三",
|
||||||
@@ -100,7 +114,8 @@ func TestRelationEngine(t *testing.T) {
|
|||||||
"age": 22,
|
"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}}))
|
// t.Log("execute error", engine.Execute(TestTable{nil, 30, "张三", map[string]any{"field": 1}, false, &struct{ Name string }{"李四"}, []any{1, "a", false}}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
module git.fsdpf.net/go/condition
|
module git.fsdpf.net/go/condition
|
||||||
|
|
||||||
go 1.21
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b
|
git.fsdpf.net/go/db v0.0.0-20260820090948-9c6bb5a83508
|
||||||
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758
|
git.fsdpf.net/go/reflux v0.0.0-20260820092857-1f48635da3f1
|
||||||
|
git.fsdpf.net/go/req v0.0.0-20260820100114-1d95bf64a2a0
|
||||||
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff
|
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff
|
||||||
github.com/samber/lo v1.39.0
|
github.com/samber/lo v1.49.1
|
||||||
github.com/spf13/cast v1.6.0
|
github.com/spf13/cast v1.10.0
|
||||||
|
github.com/stretchr/testify v1.11.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-chi/chi/v5 v5.0.12 // indirect
|
github.com/go-chi/chi/v5 v5.0.12 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/mattn/go-sqlite3 v1.14.42 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/tidwall/gjson v1.17.1 // indirect
|
github.com/tidwall/gjson v1.17.1 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,35 +1,43 @@
|
|||||||
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b h1:fRgWNOQ9dAYuUZHQ24oi1XqRbJIcJvZGbnQDaAKI7IY=
|
git.fsdpf.net/go/db v0.0.0-20260820090948-9c6bb5a83508 h1:xv2SV/Qb0TKJFnzfBtSKSeXwa6NZPRBVoGfWDahsezo=
|
||||||
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
|
git.fsdpf.net/go/db v0.0.0-20260820090948-9c6bb5a83508/go.mod h1:oDPmvCdZn/vTpbuPYPYkjdrhpis/XtMIi3eni4sYRvM=
|
||||||
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20 h1:PhjRtac2r0Vn1WBBzhRhz4//bJhHgkf/SHZe3mQYPto=
|
git.fsdpf.net/go/reflux v0.0.0-20260820092857-1f48635da3f1 h1:TRn++7oWwtANQM5JWTQGdW6fAkWMIbIsZX6tzmnsrLQ=
|
||||||
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
|
git.fsdpf.net/go/reflux v0.0.0-20260820092857-1f48635da3f1/go.mod h1:8OLCLMUwdsZ8u2sRI9y/qaE4fTf5d0SrCJ6Zn+lttFY=
|
||||||
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56 h1:7ElgXUzt75brzOr44mItipTQTk++kZNoGOyTWHLxkTU=
|
git.fsdpf.net/go/req v0.0.0-20260820100114-1d95bf64a2a0 h1:HWM1soZb13IIQA69M8DgyLYu9goz7PArtWI2mQay918=
|
||||||
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
|
git.fsdpf.net/go/req v0.0.0-20260820100114-1d95bf64a2a0/go.mod h1:Lm8WoA8EYs2696rmlxpySoCQY26Qh97cDxVF6NLBfZ8=
|
||||||
git.fsdpf.net/go/req v0.0.0-20240509033936-f8a11598db60 h1:hHrEcUSbtajC2LAo2fvEo7yrSSDeM/s5FAuQluP7yZQ=
|
|
||||||
git.fsdpf.net/go/req v0.0.0-20240509033936-f8a11598db60/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
|
|
||||||
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758 h1:nHys8Bwb29b/FvnE100KVH62aD2j5CEPxTQ/RRHo27k=
|
|
||||||
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
|
|
||||||
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff h1:1hokQGKGIstoI7WDBir2N24OGgnussZ3GmucK4TQRuI=
|
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff h1:1hokQGKGIstoI7WDBir2N24OGgnussZ3GmucK4TQRuI=
|
||||||
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff/go.mod h1:NUoyQtFr905YT+pi850mvSf4YX0WghQIcMQkTvize5o=
|
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff/go.mod h1:NUoyQtFr905YT+pi850mvSf4YX0WghQIcMQkTvize5o=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||||
|
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
@@ -39,5 +47,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user