重构: Condition/Engine 迁移到 db-v2 的表达式树,替换掉旧的 db.Raw 拼字符串
ToSql 之前是手工拼接 SQL 字符串(string 类型的 conditions、手动加括号、 手动拼 AND/OR),改成用 exp.NewExpressionList 构造表达式树,跟 db-v2 其余部分统一走"先建表达式树、由方言渲染成 SQL"这条路,不再自己维护一份 字符串拼接逻辑。ConditionType/ConditionOperator 相应改成 exp.ExpressionListType/ exp.BooleanOperation 的类型别名。 TokenValue 从 condition_expr.go 里独立成 token_value.go,从 GetParam(k string) req.GlobalParams 改成 GetParam(k string) valuex.Accessor (配合 reflux/valuex),跟其它 "-v2" 项目的属性访问方式统一。engine 包 配合适配(sqlite3 用 db/engine 的新连接管理,SelectDataset 替代 db.Connection),删掉了不再需要的 EngineParam(老 GlobalParams 包装, 新 TokenValue 已经不需要这层)。 已知问题:engine.go 在没有外部传入 GlobalParams 时用 nil user 构造兜底 值,跟 NewTokenValue 现在强制要求非 nil user 冲突,TestEngine 会 panic ——这个先不修,等后面调整 Engine.Execute 让调用方能传 user 时一起解决。
This commit is contained in:
+94
-98
@@ -7,13 +7,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.fsdpf.net/go/db"
|
||||
"git.fsdpf.net/go/req"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cast"
|
||||
"git.fsdpf.net/go/db/exp"
|
||||
"git.fsdpf.net/go/db/sqlgen"
|
||||
"git.fsdpf.net/go/reflux/valuex"
|
||||
)
|
||||
|
||||
type TokenType string
|
||||
type ConditionOperator string
|
||||
type ConditionOperator = exp.BooleanOperation
|
||||
|
||||
const (
|
||||
SQL TokenType = "sql"
|
||||
@@ -23,29 +23,24 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
IS_NULL ConditionOperator = "IS NULL"
|
||||
IS_NOT_NULL ConditionOperator = "IS NOT NULL"
|
||||
EQ ConditionOperator = "="
|
||||
NE ConditionOperator = "!="
|
||||
GT ConditionOperator = ">"
|
||||
GE ConditionOperator = ">="
|
||||
LT ConditionOperator = "<"
|
||||
LE ConditionOperator = "<="
|
||||
LIKE ConditionOperator = "LIKE"
|
||||
NOT_LIKE ConditionOperator = "NOT LIKE"
|
||||
IN ConditionOperator = "IN"
|
||||
NOT_IN ConditionOperator = "NOT IN"
|
||||
REGEXP ConditionOperator = "REGEXP"
|
||||
NOT_REGEXP ConditionOperator = "NOT REGEXP"
|
||||
IS_NULL ConditionOperator = exp.IsOp
|
||||
IS_NOT_NULL ConditionOperator = exp.IsNotOp
|
||||
EQ ConditionOperator = exp.EqOp // =
|
||||
NE ConditionOperator = exp.NeqOp // != or <>
|
||||
GT ConditionOperator = exp.GtOp // >
|
||||
GE ConditionOperator = exp.GteOp // >=
|
||||
LT ConditionOperator = exp.LtOp // <
|
||||
LE ConditionOperator = exp.LteOp // <=
|
||||
LIKE ConditionOperator = exp.LikeOp // LIKE
|
||||
NOT_LIKE ConditionOperator = exp.NotLikeOp // NOT LIKE
|
||||
IN ConditionOperator = exp.InOp // IN
|
||||
NOT_IN ConditionOperator = exp.NotInOp // NOT IN
|
||||
REGEXP ConditionOperator = exp.RegexpILikeOp // REGEXP
|
||||
NOT_REGEXP ConditionOperator = exp.RegexpNotILikeOp // NOT REGEXP
|
||||
)
|
||||
|
||||
type ExprOption func(option *ConditionExpr)
|
||||
|
||||
type TokenValue interface {
|
||||
GetParam(k string) req.GlobalParams
|
||||
GetGlobalParamsUser() req.User
|
||||
}
|
||||
|
||||
type ConditionExpr struct {
|
||||
parent *Condition
|
||||
operator ConditionOperator
|
||||
@@ -81,66 +76,43 @@ func (this *ConditionExpr) AppendTo(c *Condition) {
|
||||
}
|
||||
|
||||
func (this ConditionExpr) ToSql(m TokenValue) db.Expression {
|
||||
first := "`" + this.fieldResource + "`.`" + this.field + "`"
|
||||
var first exp.Expression = db.T(this.fieldResource).Col(this.field)
|
||||
|
||||
if strings.Contains(this.field, "->") {
|
||||
first = "`" + this.fieldResource + "`." + this.field + ""
|
||||
first = db.L("?."+this.field+"", db.T(this.fieldResource))
|
||||
}
|
||||
|
||||
value := this.GetTokenSqlValue(m)
|
||||
|
||||
operator := ConditionOperator(strings.ToUpper(string(this.GetOperator())))
|
||||
|
||||
if value == "" {
|
||||
// @todo return true
|
||||
// value = "''"
|
||||
}
|
||||
|
||||
//
|
||||
secondary := ""
|
||||
switch operator {
|
||||
case IS_NULL:
|
||||
case IS_NOT_NULL:
|
||||
|
||||
secondary = ""
|
||||
case EQ, NE,
|
||||
GT, GE,
|
||||
LT, LE,
|
||||
REGEXP, NOT_REGEXP:
|
||||
|
||||
if this.GetTokenType() == SQL {
|
||||
secondary = value
|
||||
} else {
|
||||
secondary = "'" + strings.Trim(value, "'") + "'"
|
||||
}
|
||||
var value db.Expression = this.GetTokenSqlValue(m)
|
||||
|
||||
switch this.GetOperator() {
|
||||
case LIKE, NOT_LIKE:
|
||||
secondary = "'%" + strings.Trim(value, "'") + "%'"
|
||||
case IN, NOT_IN:
|
||||
secondary = "(" + lo.Ternary(value == "", "''", value) + ")"
|
||||
value = exp.NewSQLFunctionExpression("CONCAT", "%", value, "%")
|
||||
}
|
||||
|
||||
if this.fieldSqlFunc == "json_member_of" {
|
||||
if this.fieldSqlFuncParam == "" {
|
||||
return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s, JSON_ARRAY(%s))", secondary, first))
|
||||
// return db.Raw(fmt.Sprintf("%s MEMBER OF(%s)", secondary, first))
|
||||
} else {
|
||||
return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s->>'%s', JSON_ARRAY(%s))", secondary, this.fieldSqlFuncParam, first))
|
||||
// return db.Raw(fmt.Sprintf("%s MEMBER OF(%s->'%s')", secondary, first, this.FieldSqlFuncParam))
|
||||
}
|
||||
} else if this.fieldSqlFunc == "json_contains" {
|
||||
if this.fieldSqlFuncParam == "" {
|
||||
return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s, JSON_ARRAY(%s))", first, secondary))
|
||||
} else {
|
||||
return db.Raw(fmt.Sprintf("JSON_CONTAINS(%s->>'%s', JSON_ARRAY(%s))", first, this.fieldSqlFuncParam, secondary))
|
||||
}
|
||||
} else if this.fieldSqlFunc != "" && this.fieldSqlFuncParam != "" {
|
||||
first = this.fieldSqlFunc + "(" + first + ", " + this.fieldSqlFuncParam + ")"
|
||||
} else if this.fieldSqlFunc != "" {
|
||||
first = this.fieldSqlFunc + "(" + first + ")"
|
||||
if this.fieldSqlFunc == "" {
|
||||
return exp.NewBooleanExpression(this.GetOperator(), first, value)
|
||||
}
|
||||
|
||||
return db.Raw(strings.Trim(first+" "+string(operator)+" "+secondary, " "))
|
||||
switch strings.ToLower(this.fieldSqlFunc) {
|
||||
case "json_member_of":
|
||||
if this.fieldSqlFuncParam != "" {
|
||||
return exp.NewSQLFunctionExpression("JSON_CONTAINS", db.L(this.fieldSqlFuncParam, value), exp.NewSQLFunctionExpression("JSON_ARRAY", first))
|
||||
}
|
||||
return exp.NewSQLFunctionExpression("JSON_CONTAINS", value, exp.NewSQLFunctionExpression("JSON_ARRAY", first))
|
||||
case "json_contains":
|
||||
if this.fieldSqlFuncParam != "" {
|
||||
return exp.NewSQLFunctionExpression("JSON_CONTAINS", db.L(this.fieldSqlFuncParam, first), exp.NewSQLFunctionExpression("JSON_ARRAY", value))
|
||||
}
|
||||
return exp.NewSQLFunctionExpression("JSON_CONTAINS", first, exp.NewSQLFunctionExpression("JSON_ARRAY", value))
|
||||
}
|
||||
|
||||
if this.fieldSqlFuncParam == "" {
|
||||
first = exp.NewSQLFunctionExpression(this.fieldSqlFunc, first)
|
||||
} else {
|
||||
first = exp.NewSQLFunctionExpression(this.fieldSqlFunc, first, db.L(this.fieldSqlFuncParam))
|
||||
}
|
||||
|
||||
return exp.NewBooleanExpression(this.GetOperator(), first, value)
|
||||
}
|
||||
|
||||
func (this ConditionExpr) GetTokenName() string {
|
||||
@@ -151,12 +123,17 @@ func (this ConditionExpr) GetTokenType() TokenType {
|
||||
return this.tokenType
|
||||
}
|
||||
|
||||
func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string {
|
||||
func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) exp.LiteralExpression {
|
||||
if this.GetTokenType() == SQL {
|
||||
return this.token
|
||||
return db.L(this.token)
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(this.GetTokenValue(m))
|
||||
if this.operator == IS_NULL || this.operator == IS_NOT_NULL {
|
||||
return db.V(nil)
|
||||
}
|
||||
|
||||
v := this.GetTokenValue(m)
|
||||
rv := reflect.ValueOf(v)
|
||||
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
rv = reflect.Indirect(rv)
|
||||
@@ -164,51 +141,54 @@ func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string {
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Invalid:
|
||||
return ""
|
||||
return db.V("")
|
||||
case reflect.Slice:
|
||||
aStr := cast.ToStringSlice(rv.Interface())
|
||||
|
||||
for i := 0; i < len(aStr); i++ {
|
||||
if aStr[i] != "" {
|
||||
aStr[i] = "'" + strings.Trim(db.MysqlRealEscapeString(aStr[i]), "'") + "'"
|
||||
}
|
||||
}
|
||||
|
||||
// 强制使用 in
|
||||
if this.operator == EQ {
|
||||
this.operator = IN
|
||||
}
|
||||
|
||||
return strings.Join(aStr, ", ")
|
||||
return db.V(v)
|
||||
case reflect.String:
|
||||
if this.operator == IN || this.operator == NOT_IN {
|
||||
parts := strings.Split(rv.String(), ",")
|
||||
for i, p := range parts {
|
||||
parts[i] = strings.TrimSpace(p)
|
||||
}
|
||||
return db.V(parts)
|
||||
}
|
||||
case reflect.Struct:
|
||||
b, _ := json.Marshal(rv.Interface())
|
||||
return fmt.Sprintf("'%s'", b)
|
||||
default:
|
||||
return db.MysqlRealEscapeString(fmt.Sprintf("%v", rv.Interface()))
|
||||
b, _ := json.Marshal(v)
|
||||
return db.V(b)
|
||||
}
|
||||
return db.V(v)
|
||||
}
|
||||
|
||||
func (this ConditionExpr) GetTokenValue(m TokenValue) any {
|
||||
switch this.GetTokenType() {
|
||||
case PARAM:
|
||||
var result valuex.Accessor
|
||||
|
||||
if this.matchPrefix != "" {
|
||||
return m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token)).Value()
|
||||
result = m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token))
|
||||
} else {
|
||||
result = m.GetParam(this.token)
|
||||
}
|
||||
return m.GetParam(this.token).Value()
|
||||
|
||||
return result.Any()
|
||||
case STRING:
|
||||
return this.token
|
||||
case FUNC:
|
||||
switch this.token {
|
||||
case "UserID":
|
||||
return m.GetGlobalParamsUser().ID()
|
||||
return m.User().ID()
|
||||
case "UserUuid":
|
||||
return m.GetGlobalParamsUser().Uuid()
|
||||
return m.User().Uuid()
|
||||
case "UserRolesUuid":
|
||||
return m.GetGlobalParamsUser().Roles()
|
||||
return m.User().Roles()
|
||||
case "UserPlatform":
|
||||
return m.GetGlobalParamsUser().Runtime().Platform()
|
||||
return m.User().Runtime().Platform()
|
||||
case "UserSaaS":
|
||||
return m.GetGlobalParamsUser().Runtime().SaaS()
|
||||
return m.User().Runtime().SaaS()
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
@@ -247,6 +227,12 @@ func Operator(v ConditionOperator) ExprOption {
|
||||
}
|
||||
}
|
||||
|
||||
func StringOperator(op string) ExprOption {
|
||||
return func(option *ConditionExpr) {
|
||||
option.operator = ToConditionOperator(op)
|
||||
}
|
||||
}
|
||||
|
||||
func Token(token string, tType TokenType) ExprOption {
|
||||
return func(option *ConditionExpr) {
|
||||
option.token = token
|
||||
@@ -281,3 +267,13 @@ func NewExpr(rResource, rField string, opts ...ExprOption) *ConditionExpr {
|
||||
expr.SetOption(opts...)
|
||||
return expr
|
||||
}
|
||||
|
||||
func GetDBOperator(op string) ConditionOperator {
|
||||
ops := sqlgen.DefaultDialectOptions().BooleanOperatorLookup
|
||||
for t, v := range ops {
|
||||
if string(v) == strings.ToUpper(op) {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return exp.EqOp
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user