package condition import ( "encoding/json" "fmt" "reflect" "strings" "git.fsdpf.net/go/db" "git.fsdpf.net/go/req" "github.com/samber/lo" "github.com/spf13/cast" ) type TokenType string type ConditionOperator string const ( SQL TokenType = "sql" FUNC TokenType = "func" PARAM TokenType = "param" STRING TokenType = "string" ) 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" ) type ExprOption func(option *ConditionExpr) type TokenValue interface { GetParam(k string) req.GlobalParams GetGlobalParamsUser() req.User } 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 { first := "`" + this.fieldResource + "`.`" + this.field + "`" if strings.Contains(this.field, "->") { first = "`" + this.fieldResource + "`." + this.field + "" } 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, "'") + "'" } 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 { return this.token } func (this ConditionExpr) GetTokenType() TokenType { return this.tokenType } func (this *ConditionExpr) GetTokenSqlValue(m TokenValue) string { if this.GetTokenType() == SQL { return this.token } rv := reflect.ValueOf(this.GetTokenValue(m)) if rv.Kind() == reflect.Ptr { rv = reflect.Indirect(rv) } switch rv.Kind() { case reflect.Invalid: 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 strings.Join(aStr, ", ") case reflect.Struct: b, _ := json.Marshal(rv.Interface()) return fmt.Sprintf("'%s'", b) default: return db.MysqlRealEscapeString(fmt.Sprintf("%v", rv.Interface())) } } func (this ConditionExpr) GetTokenValue(m TokenValue) any { switch this.GetTokenType() { case PARAM: if this.matchPrefix != "" { return m.GetParam(fmt.Sprintf("%s.%s", this.matchPrefix, this.token)).Value() } return m.GetParam(this.token).Value() case STRING: return this.token case FUNC: switch this.token { case "UserID": return m.GetGlobalParamsUser().ID() case "UserUuid": return m.GetGlobalParamsUser().Uuid() case "UserRolesUuid": return m.GetGlobalParamsUser().Roles() case "UserPlatform": return m.GetGlobalParamsUser().Runtime().Platform() case "UserSaaS": return m.GetGlobalParamsUser().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 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 }