重构: 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:
@@ -21,3 +21,4 @@
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
.DS_Store
|
||||
|
||||
+14
-22
@@ -5,15 +5,16 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.fsdpf.net/go/db"
|
||||
"git.fsdpf.net/go/db/exp"
|
||||
"git.fsdpf.net/go/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type ConditionType string
|
||||
type ConditionType = exp.ExpressionListType
|
||||
|
||||
const (
|
||||
OR ConditionType = "OR"
|
||||
AND ConditionType = "AND"
|
||||
AND ConditionType = exp.AndType
|
||||
OR ConditionType = exp.OrType
|
||||
)
|
||||
|
||||
type Condition struct {
|
||||
@@ -92,44 +93,35 @@ func (this Condition) IsAlwaysRight() bool {
|
||||
return !lo.Some(flag, []bool{false})
|
||||
}
|
||||
|
||||
// 生成 SQL 语句
|
||||
// ToSql 生成 SQL 语句
|
||||
func (this Condition) ToSql(m TokenValue) db.Expression {
|
||||
conditions := []string{}
|
||||
conditions := []exp.Expression{}
|
||||
// 表达式
|
||||
for _, item := range this.exprs {
|
||||
if item.IsIgnoreEmptyParma(m) {
|
||||
continue
|
||||
}
|
||||
conditions = append(conditions, string(item.ToSql(m)))
|
||||
conditions = append(conditions, item.ToSql(m))
|
||||
}
|
||||
// 条件子集
|
||||
for _, item := range this.childrens {
|
||||
conditions = append(conditions, string(item.ToSql(m)))
|
||||
conditions = append(conditions, 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.IsEmpty() {
|
||||
if this.parent == nil && this.Type() == OR {
|
||||
return db.Raw("false")
|
||||
return db.V(false)
|
||||
} else if this.parent == nil && this.Type() == AND {
|
||||
return db.Raw("true")
|
||||
return db.V(true)
|
||||
}
|
||||
return db.Raw("")
|
||||
}
|
||||
|
||||
// 包裹 SQL, 避免语法表达错误
|
||||
if this.parent == nil || this.Type() == OR {
|
||||
sql = "(" + sql + ")"
|
||||
return nil
|
||||
}
|
||||
|
||||
if this.describe != "" {
|
||||
sql += "/*" + this.describe + "*/"
|
||||
return db.L("? /* ? */", exp.NewExpressionList(this.typ, conditions...), db.L(this.describe))
|
||||
}
|
||||
|
||||
return db.Raw(sql)
|
||||
return exp.NewExpressionList(this.typ, conditions...)
|
||||
}
|
||||
|
||||
// 设置条件表达式
|
||||
|
||||
+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
|
||||
}
|
||||
|
||||
+71
-70
@@ -4,16 +4,21 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"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.Connection
|
||||
var conn *db.Database
|
||||
|
||||
var defaultEngineOptions = engineOptions{
|
||||
debug: false,
|
||||
@@ -21,76 +26,73 @@ var defaultEngineOptions = engineOptions{
|
||||
}
|
||||
|
||||
func init() {
|
||||
database := db.Open(map[string]db.DBConfig{
|
||||
"condition-engine-sqlite3": {
|
||||
Driver: "sqlite3",
|
||||
File: ":memory:",
|
||||
},
|
||||
database := db_eng.Open(map[string]db_eng.DBConfig{
|
||||
"condition-engine-sqlite3": db_eng.NewDBConfig("sqlite3", db_eng.WithSQLiteFile(":memory:")),
|
||||
})
|
||||
conn = database.Connection("condition-engine-sqlite3")
|
||||
}
|
||||
|
||||
type Engine[T any] struct {
|
||||
type Engine struct {
|
||||
code string
|
||||
opts engineOptions
|
||||
g req.GlobalParams
|
||||
def func(data T, g req.GlobalParams) error
|
||||
predicates []*EngineCase[T]
|
||||
g req.UserAccessor
|
||||
def func(data reflux.R, g req.UserAccessor) error
|
||||
predicates []*EngineCase
|
||||
}
|
||||
|
||||
func (this Engine[T]) GetCode() string {
|
||||
func (this Engine) GetCode() string {
|
||||
return this.code
|
||||
}
|
||||
|
||||
// 公共参数
|
||||
func (this *Engine[T]) SetGlobalParams(g req.GlobalParams) *Engine[T] {
|
||||
func (this *Engine) SetGlobalParams(g req.UserAccessor) *Engine {
|
||||
this.g = g
|
||||
return this
|
||||
}
|
||||
|
||||
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})
|
||||
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})
|
||||
return this
|
||||
}
|
||||
|
||||
// 基础条件
|
||||
func (this *Engine[T]) Default(cb func(data T, g req.GlobalParams) error) *Engine[T] {
|
||||
func (this *Engine) Default(cb func(data reflux.R, g req.UserAccessor) error) *Engine {
|
||||
this.def = cb
|
||||
return this
|
||||
}
|
||||
|
||||
func (this Engine[T]) toField(rv reflect.Value) (string, error) {
|
||||
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()
|
||||
}
|
||||
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 "", err
|
||||
return exp.NewSQLFunctionExpression("JSON", nil).As(as), err
|
||||
}
|
||||
return fmt.Sprintf("CAST('%s' AS JSON1)", b), nil
|
||||
return exp.NewSQLFunctionExpression("JSON", string(b)).As(as), err
|
||||
case reflect.String:
|
||||
return fmt.Sprintf("CAST('%s' AS TEXT)", v), nil
|
||||
return db.Cast(db.V(v), "TEXT").As(as), nil
|
||||
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int8, reflect.Int16,
|
||||
reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint8, reflect.Uint16:
|
||||
return fmt.Sprintf("CAST(%d AS INTEGER)", v), nil
|
||||
return db.Cast(db.V(v), "INTEGER").As(as), nil
|
||||
case reflect.Float64, reflect.Float32:
|
||||
return fmt.Sprintf("CAST(%v AS REAL)", v), nil
|
||||
return db.Cast(db.V(v), "REAL").As(as), nil
|
||||
case reflect.Bool:
|
||||
return fmt.Sprintf("CAST(%v AS BOOL)", v), nil
|
||||
return db.Cast(db.V(v), "BOOL").As(as), nil
|
||||
}
|
||||
return "NULL", nil
|
||||
return db.V(nil).As(as), nil
|
||||
}
|
||||
|
||||
func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[string][]string, err error) {
|
||||
func (this Engine) toTables(rv reflect.Value, table string) (tables map[string][]db.Expression, err error) {
|
||||
rt := rv.Type()
|
||||
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
@@ -99,11 +101,11 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin
|
||||
}
|
||||
|
||||
if tables == nil {
|
||||
tables = map[string][]string{}
|
||||
tables = map[string][]db.Expression{}
|
||||
}
|
||||
|
||||
if _, ok := tables[table]; !ok {
|
||||
tables[table] = []string{}
|
||||
tables[table] = []db.Expression{}
|
||||
}
|
||||
|
||||
if rv.Kind() == reflect.Struct {
|
||||
@@ -115,10 +117,10 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else if s, err := this.toField(rv.Field(i)); err != nil {
|
||||
} 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 {
|
||||
return nil, err
|
||||
} else {
|
||||
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)))
|
||||
tables[table] = append(tables[table], col)
|
||||
}
|
||||
}
|
||||
} else if rv.Kind() == reflect.Map {
|
||||
@@ -130,10 +132,10 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else if s, err := this.toField(iter.Value()); err != nil {
|
||||
} else if col, err := this.toField(iter.Value(), iter.Key().String()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tables[table] = append(tables[table], fmt.Sprintf("%s as `%s`", s, iter.Key().String()))
|
||||
tables[table] = append(tables[table], col)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -143,66 +145,61 @@ func (this Engine[T]) toTables(rv reflect.Value, table string) (tables map[strin
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (this *Engine[T]) Execute(data T) error {
|
||||
func (this *Engine) Execute(data any) error {
|
||||
rfx := reflux.New(data)
|
||||
|
||||
tables, err := this.toTables(reflect.ValueOf(data), this.GetCode())
|
||||
tables, err := this.toTables(rfx.Raw(), this.GetCode())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
ts := []any{}
|
||||
// 加载数据
|
||||
for table, columns := range tables {
|
||||
ts = append(ts, db.Select(this.toSelectCols(columns)...).As(table))
|
||||
}
|
||||
_db := conn.From(ts...)
|
||||
|
||||
if this.g == nil {
|
||||
this.g = req.NewGlobalParam(`{}`, nil)
|
||||
this.g = req.NewGlobalParams(`{}`, nil)
|
||||
}
|
||||
|
||||
param := &EngineParam{this.g}
|
||||
param := condition.NewTokenValue(this.g)
|
||||
|
||||
cols := []any{}
|
||||
// 加载条件
|
||||
for i, p := range this.predicates {
|
||||
sql.AddSelect(db.Raw(fmt.Sprintf("IFNULL(%s, 0) as `%d`", p.ToSql(param), i)))
|
||||
cols = append(cols, exp.NewSQLFunctionExpression("IFNULL", p.ToSql(param), 0).As(strconv.Itoa(i)))
|
||||
}
|
||||
|
||||
result := map[string]int64{}
|
||||
_db = _db.Select(cols...)
|
||||
|
||||
if this.opts.debug {
|
||||
fmt.Println("sql", sql.ToSql())
|
||||
fmt.Println(_db.ToSQL())
|
||||
}
|
||||
|
||||
if _, err := sql.First(&result); err != nil {
|
||||
return fmt.Errorf("%s => %s", err, sql.ToSql())
|
||||
result, err := _db.Executor().GetRecord()
|
||||
if err != nil {
|
||||
sql, _, _ := _db.ToSQL()
|
||||
return fmt.Errorf("%s => %s", err, sql)
|
||||
}
|
||||
|
||||
isDefault := true
|
||||
for k, v := range result {
|
||||
if v == 0 {
|
||||
for fnIndex, v := range result {
|
||||
b := cast.ToInt64(v)
|
||||
if b == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
isDefault = false
|
||||
|
||||
if err := this.predicates[cast.ToInt64(k)].Execute(data, this.g); err != nil {
|
||||
return fmt.Errorf("case %q error, %s", k, err)
|
||||
if err := this.predicates[cast.ToInt64(fnIndex)].Execute(rfx, this.g); err != nil {
|
||||
return fmt.Errorf("case %q error, %s", fnIndex, err)
|
||||
}
|
||||
}
|
||||
|
||||
if isDefault && this.def != nil {
|
||||
if err := this.def(data, this.g); err != nil {
|
||||
if err := this.def(rfx, this.g); err != nil {
|
||||
return fmt.Errorf("case %q error, %s", "default", err)
|
||||
}
|
||||
}
|
||||
@@ -210,16 +207,20 @@ func (this *Engine[T]) Execute(data T) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func New[T any](table string, opt ...EngineOption) *Engine[T] {
|
||||
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 {
|
||||
opts := defaultEngineOptions
|
||||
|
||||
for _, o := range opt {
|
||||
o.apply(&opts)
|
||||
}
|
||||
|
||||
return &Engine[T]{
|
||||
return &Engine{
|
||||
code: table,
|
||||
opts: opts,
|
||||
g: req.NewGlobalParam("", nil),
|
||||
// g: req.NewParam("", nil),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,23 @@ 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[T any] struct {
|
||||
type EngineCase struct {
|
||||
predicate *condition.Condition
|
||||
cb func(data T, g req.GlobalParams) error
|
||||
cb func(data reflux.R, g req.UserAccessor) error
|
||||
}
|
||||
|
||||
func (this EngineCase[T]) ToSql(param condition.TokenValue) db.Expression {
|
||||
func (this EngineCase) ToSql(param condition.TokenValue) db.Expression {
|
||||
if this.predicate == nil || this.predicate.IsEmpty() {
|
||||
return db.Raw("NULL")
|
||||
return db.V("NULL")
|
||||
}
|
||||
|
||||
return this.predicate.ToSql(param)
|
||||
}
|
||||
|
||||
func (this EngineCase[T]) Execute(data T, g req.GlobalParams) error {
|
||||
func (this EngineCase) Execute(data reflux.R, g req.UserAccessor) error {
|
||||
return this.cb(data, g)
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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()
|
||||
}
|
||||
+35
-20
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"git.fsdpf.net/go/condition"
|
||||
"git.fsdpf.net/go/reflux"
|
||||
"git.fsdpf.net/go/req"
|
||||
)
|
||||
|
||||
@@ -18,26 +19,40 @@ type TestTable struct {
|
||||
}
|
||||
|
||||
func TestEngine(t *testing.T) {
|
||||
engine := Engine[any]{
|
||||
engine := Engine{
|
||||
code: "TestTable",
|
||||
g: req.NewGlobalParam(`{"age": 30}`, nil),
|
||||
g: req.NewGlobalParams(`{"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 any, g req.GlobalParams) error {
|
||||
t.Log("cond1", data)
|
||||
engine.Case(cond1, func(data reflux.R, g req.UserAccessor) error {
|
||||
t.Log("cond1", data.Any())
|
||||
return nil
|
||||
})
|
||||
|
||||
engine.Default(func(data any, g req.GlobalParams) error {
|
||||
t.Log("default", data)
|
||||
engine.Default(func(data reflux.R, g req.UserAccessor) error {
|
||||
t.Log("default", data.Any())
|
||||
return nil
|
||||
})
|
||||
|
||||
engine.Execute(map[string]any{
|
||||
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{
|
||||
"null": nil,
|
||||
"age": 30,
|
||||
"name": "张三",
|
||||
@@ -47,15 +62,14 @@ func TestEngine(t *testing.T) {
|
||||
"bool": false,
|
||||
"struct": &struct{ Name string }{"李四"},
|
||||
"array": []any{1, "a", false},
|
||||
})
|
||||
|
||||
t.Log("execute error", engine.Execute(TestTable{nil, 30, "张三", map[string]any{"field": 1}, false, &struct{ Name string }{"李四"}, []any{1, "a", false}}))
|
||||
}
|
||||
t.Log("execute error", engine.Execute(testData2))
|
||||
}
|
||||
|
||||
func TestRelationEngine(t *testing.T) {
|
||||
engine := Engine[any]{
|
||||
engine := Engine{
|
||||
code: "TestTable",
|
||||
g: req.NewGlobalParam(`{"age": 30}`, nil),
|
||||
g: req.NewGlobalParams(`{"age": 30}`, nil),
|
||||
opts: engineOptions{
|
||||
debug: false,
|
||||
relations: []string{"TestTableA"},
|
||||
@@ -65,8 +79,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 any, g req.GlobalParams) error {
|
||||
t.Log("cond1", data)
|
||||
engine.Case(cond1, func(data reflux.R, g req.UserAccessor) error {
|
||||
t.Log("cond1", data.Any())
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -74,17 +88,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 any, g req.GlobalParams) error {
|
||||
t.Log("cond2", data)
|
||||
engine.Case(cond2, func(data reflux.R, g req.UserAccessor) error {
|
||||
t.Log("cond2", data.Any())
|
||||
return nil
|
||||
})
|
||||
|
||||
engine.Default(func(data any, g req.GlobalParams) error {
|
||||
t.Log("default", data)
|
||||
engine.Default(func(data reflux.R, g req.UserAccessor) error {
|
||||
t.Log("default", data.Any())
|
||||
return nil
|
||||
})
|
||||
|
||||
engine.Execute(map[string]any{
|
||||
testData := map[string]any{
|
||||
"null": nil,
|
||||
"age": 31,
|
||||
"name": "张三",
|
||||
@@ -100,7 +114,8 @@ 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}}))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user