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 }