Compare commits

1 Commits
Author SHA1 Message Date
what ed77aa4648 docs: 补充 README,说明 v1-legacy 分支的能力范围与安装方式
记录这条老线跟 master 的差异,以及依赖方(如 zg-estate)应该怎么锁定/更新
到这个分支,避免误跑 go get -u 拉到 master 上的破坏性改动。
2026-08-21 09:05:24 +08:00
15 changed files with 293 additions and 735 deletions
-1
View File
@@ -21,4 +21,3 @@
# Go workspace file
go.work
.DS_Store
+28 -44
View File
@@ -1,54 +1,38 @@
# git.fsdpf.net/go/conditionmaster
# git.fsdpf.net/go/conditionv1-legacy
条件表达式 DSL:把一组条件(等值/比较/模糊匹配/IN 等)组织成可嵌套的 `AND`/`OR` 树,渲染成
`db-v2` 的表达式(`db.Expression`),供权限过滤、规则引擎等场景复用同一套条件描述。配合
"-v2" 系列项目使用
老架构下的条件表达式 DSL:把一组条件(等值/比较/模糊匹配/IN 等)组织成可嵌套的 `AND`/`OR`
树,`ToSql` 手工拼接成 SQL 字符串(`db.Raw(...)`),供权限过滤、规则引擎等场景复用同一套
条件描述
## 这个仓库有什么
## 这个分支有什么
### `Condition`/`ConditionExpr`
- `Condition`:条件树节点,`ConditionType``AND`/`OR`)是独立的 `string` 类型,`ToSql(m
TokenValue) db.Expression` 内部把子条件/表达式各自渲染成字符串、`strings.Join` 拼接、手动
加括号、返回 `db.Raw(sql)`——不是表达式树,是纯字符串拼接。
- `ConditionExpr`:单个表达式(字段 + 操作符 + 值),`ConditionOperator` 是独立的 `string`
类型(`"="`/`"!="`/`"LIKE"`/`"IN"` 等字符串常量),`TokenValue` 接口定义在这个文件里
`GetParam(k string) req.GlobalParams`,不是新架构的 `valuex.Accessor`)。
- `engine/``Engine[T]` 规则引擎,`EngineParam` 包一层 `req.GlobalParams` 给条件求值用。
- `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`
给还在维护、但还没迁移到新架构(`master` 分支上 `Condition.ToSql` 改用 `db-v2` 表达式树、
`TokenValue` 改用 `reflux/valuex`、新增 JSON 序列化支持)的老项目用,比如 `zg-estate`(间接
依赖)。只接受独立于新架构的 bug 修复。
条件渲染时取值用的接口:`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`
Go modules 不会记住某个版本是从哪个分支解析出来的,`go.mod`/`go.sum` 里存的只是一次性解析出
的 commit 伪版本号,分支上有新提交也不会自动同步。
`Condition`/`ConditionExpr` 都实现了 `MarshalJSON`/`UnmarshalJSON`(内部过一层 DTO 结构避免
递归),`FromMap(m map[string]any) (*Condition, error)` 直接从已解析好的 map 构造,不用先转
成 JSON 字符串再解析一遍——典型场景是权限配置、规则条件存在数据库的 JSON 列里,读出来直接
反序列化成 `Condition` 用。
**首次锁定,或者要拿这个分支上新提交的更新**,都执行同一条命令:
### `engine` 子包:规则引擎
```
go get git.fsdpf.net/go/condition@v1-legacy
```
`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` 分支。
**禁止**跑裸的 `go get -u`(或者 `go get git.fsdpf.net/go/condition@latest`)——这个仓库没有
打语义化 tagGo 对"最新版本"的解析规则是退回到仓库默认分支(`master`)的 HEAD,会把新架构
的破坏性改动一起拉进来,而不是停留在这个分支上。同时注意这条老线依赖的
`git.fsdpf.net/go/db`/`git.fsdpf.net/go/req` 也要一起锁定在它们各自的 `v1-legacy` 分支,
不能新老混用
+25 -17
View File
@@ -5,16 +5,15 @@ import (
"strings"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/utils"
"github.com/samber/lo"
)
type ConditionType = exp.ExpressionListType
type ConditionType string
const (
AND ConditionType = exp.AndType
OR ConditionType = exp.OrType
OR ConditionType = "OR"
AND ConditionType = "AND"
)
type Condition struct {
@@ -93,35 +92,44 @@ func (this Condition) IsAlwaysRight() bool {
return !lo.Some(flag, []bool{false})
}
// ToSql 生成 SQL 语句
// 生成 SQL 语句
func (this Condition) ToSql(m TokenValue) db.Expression {
conditions := []exp.Expression{}
conditions := []string{}
// 表达式
for _, item := range this.exprs {
if item.IsIgnoreEmptyParma(m) {
continue
}
conditions = append(conditions, item.ToSql(m))
conditions = append(conditions, string(item.ToSql(m)))
}
// 条件子集
for _, item := range this.childrens {
conditions = append(conditions, item.ToSql(m))
conditions = append(conditions, string(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.parent == nil && this.Type() == OR {
return db.Raw("false")
} else if this.parent == nil && this.Type() == AND {
return db.Raw("true")
}
return db.Raw("")
}
if this.IsEmpty() {
if this.parent == nil && this.Type() == OR {
return db.V(false)
} else if this.parent == nil && this.Type() == AND {
return db.V(true)
}
return nil
// 包裹 SQL, 避免语法表达错误
if this.parent == nil || this.Type() == OR {
sql = "(" + sql + ")"
}
if this.describe != "" {
return db.L("? /* ? */", exp.NewExpressionList(this.typ, conditions...), db.L(this.describe))
sql += "/*" + this.describe + "*/"
}
return exp.NewExpressionList(this.typ, conditions...)
return db.Raw(sql)
}
// 设置条件表达式
+96 -92
View File
@@ -7,13 +7,13 @@ import (
"strings"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/sqlgen"
"git.fsdpf.net/go/reflux/valuex"
"git.fsdpf.net/go/req"
"github.com/samber/lo"
"github.com/spf13/cast"
)
type TokenType string
type ConditionOperator = exp.BooleanOperation
type ConditionOperator string
const (
SQL TokenType = "sql"
@@ -23,24 +23,29 @@ const (
)
const (
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
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"
)
type ExprOption func(option *ConditionExpr)
type TokenValue interface {
GetParam(k string) req.GlobalParams
GetGlobalParamsUser() req.User
}
type ConditionExpr struct {
parent *Condition
operator ConditionOperator
@@ -76,43 +81,66 @@ func (this *ConditionExpr) AppendTo(c *Condition) {
}
func (this ConditionExpr) ToSql(m TokenValue) db.Expression {
var first exp.Expression = db.T(this.fieldResource).Col(this.field)
first := "`" + this.fieldResource + "`.`" + this.field + "`"
if strings.Contains(this.field, "->") {
first = db.L("?."+this.field+"", db.T(this.fieldResource))
first = "`" + this.fieldResource + "`." + this.field + ""
}
var value db.Expression = this.GetTokenSqlValue(m)
value := this.GetTokenSqlValue(m)
switch this.GetOperator() {
case LIKE, NOT_LIKE:
value = exp.NewSQLFunctionExpression("CONCAT", "%", value, "%")
operator := ConditionOperator(strings.ToUpper(string(this.GetOperator())))
if value == "" {
// @todo return true
// value = "''"
}
if this.fieldSqlFunc == "" {
return exp.NewBooleanExpression(this.GetOperator(), first, value)
}
//
secondary := ""
switch operator {
case IS_NULL:
case IS_NOT_NULL:
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))
}
secondary = ""
case EQ, NE,
GT, GE,
LT, LE,
REGEXP, NOT_REGEXP:
if this.fieldSqlFuncParam == "" {
first = exp.NewSQLFunctionExpression(this.fieldSqlFunc, first)
if this.GetTokenType() == SQL {
secondary = value
} else {
first = exp.NewSQLFunctionExpression(this.fieldSqlFunc, first, db.L(this.fieldSqlFuncParam))
secondary = "'" + strings.Trim(value, "'") + "'"
}
return exp.NewBooleanExpression(this.GetOperator(), first, value)
case LIKE, NOT_LIKE:
secondary = "'%" + strings.Trim(value, "'") + "%'"
case IN, NOT_IN:
secondary = "(" + lo.Ternary(value == "", "''", 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 + ")"
}
return db.Raw(strings.Trim(first+" "+string(operator)+" "+secondary, " "))
}
func (this ConditionExpr) GetTokenName() string {
@@ -123,17 +151,12 @@ func (this ConditionExpr) GetTokenType() TokenType {
return this.tokenType
}
func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) exp.LiteralExpression {
func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string {
if this.GetTokenType() == SQL {
return db.L(this.token)
return this.token
}
if this.operator == IS_NULL || this.operator == IS_NOT_NULL {
return db.V(nil)
}
v := this.GetTokenValue(m)
rv := reflect.ValueOf(v)
rv := reflect.ValueOf(this.GetTokenValue(m))
if rv.Kind() == reflect.Ptr {
rv = reflect.Indirect(rv)
@@ -141,54 +164,51 @@ func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) exp.LiteralExpression
switch rv.Kind() {
case reflect.Invalid:
return db.V("")
return ""
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 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)
}
return strings.Join(aStr, ", ")
case reflect.Struct:
b, _ := json.Marshal(v)
return db.V(b)
b, _ := json.Marshal(rv.Interface())
return fmt.Sprintf("'%s'", b)
default:
return db.MysqlRealEscapeString(fmt.Sprintf("%v", rv.Interface()))
}
return db.V(v)
}
func (this ConditionExpr) GetTokenValue(m TokenValue) any {
switch this.GetTokenType() {
case PARAM:
var result valuex.Accessor
if this.matchPrefix != "" {
result = m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token))
} else {
result = m.GetParam(this.token)
return m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token)).Value()
}
return result.Any()
return m.GetParam(this.token).Value()
case STRING:
return this.token
case FUNC:
switch this.token {
case "UserID":
return m.User().ID()
return m.GetGlobalParamsUser().ID()
case "UserUuid":
return m.User().Uuid()
return m.GetGlobalParamsUser().Uuid()
case "UserRolesUuid":
return m.User().Roles()
return m.GetGlobalParamsUser().Roles()
case "UserPlatform":
return m.User().Runtime().Platform()
return m.GetGlobalParamsUser().Runtime().Platform()
case "UserSaaS":
return m.User().Runtime().SaaS()
return m.GetGlobalParamsUser().Runtime().SaaS()
}
default:
return nil
@@ -227,12 +247,6 @@ 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
@@ -267,13 +281,3 @@ 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
}
-161
View File
@@ -1,161 +0,0 @@
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 "="
}
}
-126
View File
@@ -1,126 +0,0 @@
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())
}
-41
View File
@@ -1,41 +0,0 @@
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())
}
+70 -71
View File
@@ -4,21 +4,16 @@ import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"git.fsdpf.net/go/condition"
"git.fsdpf.net/go/db"
_ "git.fsdpf.net/go/db/dialect/sqlite3"
db_eng "git.fsdpf.net/go/db/engine"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req"
"github.com/samber/lo"
"github.com/spf13/cast"
)
var conn *db.Database
var conn *db.Connection
var defaultEngineOptions = engineOptions{
debug: false,
@@ -26,73 +21,76 @@ var defaultEngineOptions = engineOptions{
}
func init() {
database := db_eng.Open(map[string]db_eng.DBConfig{
"condition-engine-sqlite3": db_eng.NewDBConfig("sqlite3", db_eng.WithSQLiteFile(":memory:")),
database := db.Open(map[string]db.DBConfig{
"condition-engine-sqlite3": {
Driver: "sqlite3",
File: ":memory:",
},
})
conn = database.Connection("condition-engine-sqlite3")
}
type Engine struct {
type Engine[T any] struct {
code string
opts engineOptions
g req.UserAccessor
def func(data reflux.R, g req.UserAccessor) error
predicates []*EngineCase
g req.GlobalParams
def func(data T, g req.GlobalParams) error
predicates []*EngineCase[T]
}
func (this Engine) GetCode() string {
func (this Engine[T]) GetCode() string {
return this.code
}
// 公共参数
func (this *Engine) SetGlobalParams(g req.UserAccessor) *Engine {
func (this *Engine[T]) SetGlobalParams(g req.GlobalParams) *Engine[T] {
this.g = g
return this
}
func (this *Engine) Case(cond *condition.Condition, cb func(data reflux.R, g req.UserAccessor) error) *Engine {
this.predicates = append(this.predicates, &EngineCase{cond, cb})
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})
return this
}
// 基础条件
func (this *Engine) Default(cb func(data reflux.R, g req.UserAccessor) error) *Engine {
func (this *Engine[T]) Default(cb func(data T, g req.GlobalParams) error) *Engine[T] {
this.def = cb
return this
}
func (this Engine) toField(rv reflect.Value, as string) (db.Expression, error) {
if rv.IsZero() {
return db.V(nil).As(as), nil
}
if rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
func (this Engine[T]) toField(rv reflect.Value) (string, error) {
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 exp.NewSQLFunctionExpression("JSON", nil).As(as), err
return "", err
}
return exp.NewSQLFunctionExpression("JSON", string(b)).As(as), err
return fmt.Sprintf("CAST('%s' AS JSON1)", b), nil
case reflect.String:
return db.Cast(db.V(v), "TEXT").As(as), nil
return fmt.Sprintf("CAST('%s' AS TEXT)", v), nil
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int8, reflect.Int16,
reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint8, reflect.Uint16:
return db.Cast(db.V(v), "INTEGER").As(as), nil
return fmt.Sprintf("CAST(%d AS INTEGER)", v), nil
case reflect.Float64, reflect.Float32:
return db.Cast(db.V(v), "REAL").As(as), nil
return fmt.Sprintf("CAST(%v AS REAL)", v), nil
case reflect.Bool:
return db.Cast(db.V(v), "BOOL").As(as), nil
return fmt.Sprintf("CAST(%v AS BOOL)", v), nil
}
return db.V(nil).As(as), nil
return "NULL", nil
}
func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][]db.Expression, err error) {
func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[string][]string, err error) {
rt := rv.Type()
if rv.Kind() == reflect.Ptr {
@@ -101,11 +99,11 @@ func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][
}
if tables == nil {
tables = map[string][]db.Expression{}
tables = map[string][]string{}
}
if _, ok := tables[table]; !ok {
tables[table] = []db.Expression{}
tables[table] = []string{}
}
if rv.Kind() == reflect.Struct {
@@ -117,10 +115,10 @@ func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][
} else {
return nil, err
}
} else if col, err := this.toField(rv.Field(i), lo.Ternary(rt.Field(i).Tag.Get("db") != "", rt.Field(i).Tag.Get("db"), rt.Field(i).Name)); err != nil {
} else if s, err := this.toField(rv.Field(i)); err != nil {
return nil, err
} else {
tables[table] = append(tables[table], col)
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)))
}
}
} else if rv.Kind() == reflect.Map {
@@ -132,10 +130,10 @@ func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][
} else {
return nil, err
}
} else if col, err := this.toField(iter.Value(), iter.Key().String()); err != nil {
} else if s, err := this.toField(iter.Value()); err != nil {
return nil, err
} else {
tables[table] = append(tables[table], col)
tables[table] = append(tables[table], fmt.Sprintf("%s as `%s`", s, iter.Key().String()))
}
}
} else {
@@ -145,61 +143,66 @@ func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][
return tables, nil
}
func (this *Engine) Execute(data any) error {
rfx := reflux.New(data)
func (this *Engine[T]) Execute(data T) error {
tables, err := this.toTables(rfx.Raw(), this.GetCode())
tables, err := this.toTables(reflect.ValueOf(data), this.GetCode())
if err != nil {
return err
}
ts := []any{}
// 加载数据
for table, columns := range tables {
ts = append(ts, db.Select(this.toSelectCols(columns)...).As(table))
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),
)
}
_db := conn.From(ts...)
if this.g == nil {
this.g = req.NewGlobalParams(`{}`, nil)
this.g = req.NewGlobalParam(`{}`, nil)
}
param := condition.NewTokenValue(this.g)
param := &EngineParam{this.g}
cols := []any{}
// 加载条件
for i, p := range this.predicates {
cols = append(cols, exp.NewSQLFunctionExpression("IFNULL", p.ToSql(param), 0).As(strconv.Itoa(i)))
sql.AddSelect(db.Raw(fmt.Sprintf("IFNULL(%s, 0) as `%d`", p.ToSql(param), i)))
}
_db = _db.Select(cols...)
result := map[string]int64{}
if this.opts.debug {
fmt.Println(_db.ToSQL())
fmt.Println("sql", sql.ToSql())
}
result, err := _db.Executor().GetRecord()
if err != nil {
sql, _, _ := _db.ToSQL()
return fmt.Errorf("%s => %s", err, sql)
if _, err := sql.First(&result); err != nil {
return fmt.Errorf("%s => %s", err, sql.ToSql())
}
isDefault := true
for fnIndex, v := range result {
b := cast.ToInt64(v)
if b == 0 {
for k, v := range result {
if v == 0 {
continue
}
isDefault = false
if err := this.predicates[cast.ToInt64(fnIndex)].Execute(rfx, this.g); err != nil {
return fmt.Errorf("case %q error, %s", fnIndex, err)
if err := this.predicates[cast.ToInt64(k)].Execute(data, this.g); err != nil {
return fmt.Errorf("case %q error, %s", k, err)
}
}
if isDefault && this.def != nil {
if err := this.def(rfx, this.g); err != nil {
if err := this.def(data, this.g); err != nil {
return fmt.Errorf("case %q error, %s", "default", err)
}
}
@@ -207,20 +210,16 @@ func (this *Engine) Execute(data any) error {
return nil
}
func (this *Engine) toSelectCols(cols []db.Expression) []any {
return lo.Map(cols, func(item db.Expression, _ int) any { return item })
}
func New(table string, opt ...EngineOption) *Engine {
func New[T any](table string, opt ...EngineOption) *Engine[T] {
opts := defaultEngineOptions
for _, o := range opt {
o.apply(&opts)
}
return &Engine{
return &Engine[T]{
code: table,
opts: opts,
// g: req.NewParam("", nil),
g: req.NewGlobalParam("", nil),
}
}
+5 -6
View File
@@ -3,23 +3,22 @@ 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 struct {
type EngineCase[T any] struct {
predicate *condition.Condition
cb func(data reflux.R, g req.UserAccessor) error
cb func(data T, g req.GlobalParams) error
}
func (this EngineCase) ToSql(param condition.TokenValue) db.Expression {
func (this EngineCase[T]) ToSql(param condition.TokenValue) db.Expression {
if this.predicate == nil || this.predicate.IsEmpty() {
return db.V("NULL")
return db.Raw("NULL")
}
return this.predicate.ToSql(param)
}
func (this EngineCase) Execute(data reflux.R, g req.UserAccessor) error {
func (this EngineCase[T]) Execute(data T, g req.GlobalParams) error {
return this.cb(data, g)
}
+17
View File
@@ -0,0 +1,17 @@
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()
}
+20 -35
View File
@@ -4,7 +4,6 @@ import (
"testing"
"git.fsdpf.net/go/condition"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req"
)
@@ -19,40 +18,26 @@ type TestTable struct {
}
func TestEngine(t *testing.T) {
engine := Engine{
engine := Engine[any]{
code: "TestTable",
g: req.NewGlobalParams(`{"age": 30}`, nil),
g: req.NewGlobalParam(`{"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 reflux.R, g req.UserAccessor) error {
t.Log("cond1", data.Any())
engine.Case(cond1, func(data any, g req.GlobalParams) error {
t.Log("cond1", data)
return nil
})
engine.Default(func(data reflux.R, g req.UserAccessor) error {
t.Log("default", data.Any())
engine.Default(func(data any, g req.GlobalParams) error {
t.Log("default", data)
return nil
})
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{
engine.Execute(map[string]any{
"null": nil,
"age": 30,
"name": "张三",
@@ -62,14 +47,15 @@ func TestEngine(t *testing.T) {
"bool": false,
"struct": &struct{ Name string }{"李四"},
"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) {
engine := Engine{
engine := Engine[any]{
code: "TestTable",
g: req.NewGlobalParams(`{"age": 30}`, nil),
g: req.NewGlobalParam(`{"age": 30}`, nil),
opts: engineOptions{
debug: false,
relations: []string{"TestTableA"},
@@ -79,8 +65,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 reflux.R, g req.UserAccessor) error {
t.Log("cond1", data.Any())
engine.Case(cond1, func(data any, g req.GlobalParams) error {
t.Log("cond1", data)
return nil
})
@@ -88,17 +74,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 reflux.R, g req.UserAccessor) error {
t.Log("cond2", data.Any())
engine.Case(cond2, func(data any, g req.GlobalParams) error {
t.Log("cond2", data)
return nil
})
engine.Default(func(data reflux.R, g req.UserAccessor) error {
t.Log("default", data.Any())
engine.Default(func(data any, g req.GlobalParams) error {
t.Log("default", data)
return nil
})
testData := map[string]any{
engine.Execute(map[string]any{
"null": nil,
"age": 31,
"name": "张三",
@@ -114,8 +100,7 @@ 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}}))
}
+8 -12
View File
@@ -1,26 +1,22 @@
module git.fsdpf.net/go/condition
go 1.25.5
go 1.21
require (
git.fsdpf.net/go/db v0.0.0-20260820090948-9c6bb5a83508
git.fsdpf.net/go/reflux v0.0.0-20260820092857-1f48635da3f1
git.fsdpf.net/go/req v0.0.0-20260820100114-1d95bf64a2a0
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff
github.com/samber/lo v1.49.1
github.com/spf13/cast v1.10.0
github.com/stretchr/testify v1.11.1
github.com/samber/lo v1.39.0
github.com/spf13/cast v1.6.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/mattn/go-sqlite3 v1.14.42 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
golang.org/x/text v0.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
)
+24 -37
View File
@@ -1,43 +1,35 @@
git.fsdpf.net/go/db v0.0.0-20260820090948-9c6bb5a83508 h1:xv2SV/Qb0TKJFnzfBtSKSeXwa6NZPRBVoGfWDahsezo=
git.fsdpf.net/go/db v0.0.0-20260820090948-9c6bb5a83508/go.mod h1:oDPmvCdZn/vTpbuPYPYkjdrhpis/XtMIi3eni4sYRvM=
git.fsdpf.net/go/reflux v0.0.0-20260820092857-1f48635da3f1 h1:TRn++7oWwtANQM5JWTQGdW6fAkWMIbIsZX6tzmnsrLQ=
git.fsdpf.net/go/reflux v0.0.0-20260820092857-1f48635da3f1/go.mod h1:8OLCLMUwdsZ8u2sRI9y/qaE4fTf5d0SrCJ6Zn+lttFY=
git.fsdpf.net/go/req v0.0.0-20260820100114-1d95bf64a2a0 h1:HWM1soZb13IIQA69M8DgyLYu9goz7PArtWI2mQay918=
git.fsdpf.net/go/req v0.0.0-20260820100114-1d95bf64a2a0/go.mod h1:Lm8WoA8EYs2696rmlxpySoCQY26Qh97cDxVF6NLBfZ8=
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b h1:fRgWNOQ9dAYuUZHQ24oi1XqRbJIcJvZGbnQDaAKI7IY=
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20 h1:PhjRtac2r0Vn1WBBzhRhz4//bJhHgkf/SHZe3mQYPto=
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56 h1:7ElgXUzt75brzOr44mItipTQTk++kZNoGOyTWHLxkTU=
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
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/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/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/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
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/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
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/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -47,10 +39,5 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
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/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
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=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
-53
View File
@@ -1,53 +0,0 @@
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
@@ -1,39 +0,0 @@
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
}