Files
condition/condition_expr.go
T
what 5b39565a36 重构: Condition/Engine 迁移到 db-v2 的表达式树,替换掉旧的 db.Raw 拼字符串
ToSql 之前是手工拼接 SQL 字符串(string 类型的 conditions、手动加括号、
手动拼 AND/OR),改成用 exp.NewExpressionList 构造表达式树,跟 db-v2
其余部分统一走"先建表达式树、由方言渲染成 SQL"这条路,不再自己维护一份
字符串拼接逻辑。ConditionType/ConditionOperator 相应改成 exp.ExpressionListType/
exp.BooleanOperation 的类型别名。

TokenValue 从 condition_expr.go 里独立成 token_value.go,从
GetParam(k string) req.GlobalParams 改成 GetParam(k string) valuex.Accessor
(配合 reflux/valuex),跟其它 "-v2" 项目的属性访问方式统一。engine 包
配合适配(sqlite3 用 db/engine 的新连接管理,SelectDataset 替代
db.Connection),删掉了不再需要的 EngineParam(老 GlobalParams 包装,
新 TokenValue 已经不需要这层)。

已知问题:engine.go 在没有外部传入 GlobalParams 时用 nil user 构造兜底
值,跟 NewTokenValue 现在强制要求非 nil user 冲突,TestEngine 会 panic
——这个先不修,等后面调整 Engine.Execute 让调用方能传 user 时一起解决。
2026-08-21 08:55:21 +08:00

280 lines
6.6 KiB
Go

package condition
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/sqlgen"
"git.fsdpf.net/go/reflux/valuex"
)
type TokenType string
type ConditionOperator = exp.BooleanOperation
const (
SQL TokenType = "sql"
FUNC TokenType = "func"
PARAM TokenType = "param"
STRING TokenType = "string"
)
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
)
type ExprOption func(option *ConditionExpr)
type ConditionExpr struct {
parent *Condition
operator ConditionOperator
field string
fieldResource string
fieldSqlFunc string
fieldSqlFuncParam string
ignoreEmptyParma bool
tokenType TokenType
token string
matchPrefix string // 匹配前缀
}
func (this ConditionExpr) GetOperator() ConditionOperator {
return this.operator
}
func (this *ConditionExpr) SetMatchPrefix(s string) *ConditionExpr {
this.matchPrefix = s
return this
}
func (this ConditionExpr) GetField() string {
return this.field
}
func (this ConditionExpr) GetFieldResource() string {
return this.fieldResource
}
func (this *ConditionExpr) AppendTo(c *Condition) {
this.parent = c
}
func (this ConditionExpr) ToSql(m TokenValue) db.Expression {
var first exp.Expression = db.T(this.fieldResource).Col(this.field)
if strings.Contains(this.field, "->") {
first = db.L("?."+this.field+"", db.T(this.fieldResource))
}
var value db.Expression = this.GetTokenSqlValue(m)
switch this.GetOperator() {
case LIKE, NOT_LIKE:
value = exp.NewSQLFunctionExpression("CONCAT", "%", value, "%")
}
if this.fieldSqlFunc == "" {
return exp.NewBooleanExpression(this.GetOperator(), first, value)
}
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 {
return this.token
}
func (this ConditionExpr) GetTokenType() TokenType {
return this.tokenType
}
func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) exp.LiteralExpression {
if this.GetTokenType() == SQL {
return db.L(this.token)
}
if this.operator == IS_NULL || this.operator == IS_NOT_NULL {
return db.V(nil)
}
v := this.GetTokenValue(m)
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = reflect.Indirect(rv)
}
switch rv.Kind() {
case reflect.Invalid:
return db.V("")
case reflect.Slice:
// 强制使用 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)
}
case reflect.Struct:
b, _ := json.Marshal(v)
return db.V(b)
}
return db.V(v)
}
func (this ConditionExpr) GetTokenValue(m TokenValue) any {
switch this.GetTokenType() {
case PARAM:
var result valuex.Accessor
if this.matchPrefix != "" {
result = m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token))
} else {
result = m.GetParam(this.token)
}
return result.Any()
case STRING:
return this.token
case FUNC:
switch this.token {
case "UserID":
return m.User().ID()
case "UserUuid":
return m.User().Uuid()
case "UserRolesUuid":
return m.User().Roles()
case "UserPlatform":
return m.User().Runtime().Platform()
case "UserSaaS":
return m.User().Runtime().SaaS()
}
default:
return nil
}
return nil
}
func (this ConditionExpr) IsIgnoreEmptyParma(m TokenValue) bool {
if !this.ignoreEmptyParma {
return false
}
if this.tokenType != PARAM {
return false
}
param := this.GetTokenValue(m)
if param == "" || param == nil {
return true
}
return false
}
func (this *ConditionExpr) SetOption(opts ...ExprOption) *ConditionExpr {
for i := 0; i < len(opts); i++ {
opts[i](this)
}
return this
}
func Operator(v ConditionOperator) ExprOption {
return func(option *ConditionExpr) {
option.operator = v
}
}
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
option.tokenType = tType
}
}
func IgnoreEmptyParma(v bool) ExprOption {
return func(option *ConditionExpr) {
option.ignoreEmptyParma = v
}
}
func FieldSqlFn(fn, fnParam string) ExprOption {
return func(option *ConditionExpr) {
option.fieldSqlFunc = fn
option.fieldSqlFuncParam = fnParam
}
}
func NewExpr(rResource, rField string, opts ...ExprOption) *ConditionExpr {
expr := &ConditionExpr{
field: rField,
fieldResource: rResource,
token: "",
tokenType: STRING,
operator: EQ,
ignoreEmptyParma: false,
fieldSqlFunc: "",
fieldSqlFuncParam: "",
}
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
}