-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
chenyunfei.cs
committed
Aug 28, 2024
0 parents
commit f14585a
Showing
14 changed files
with
1,466 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
package ormx | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
|
||
sb "github.com/huandu/go-sqlbuilder" | ||
) | ||
|
||
type Builder interface { | ||
Build() (string, []any) | ||
} | ||
|
||
// Build is same with builder.Build, but it will try to inject namespace(which defined in context) filter into where condition in sql | ||
func Build(ctx context.Context, b Builder) (string, []any) { | ||
switch x := b.(type) { | ||
case *sb.UpdateBuilder: | ||
appendNamespaceFilter(ctx, &x.Cond) | ||
case *sb.SelectBuilder: | ||
appendNamespaceFilter(ctx, &x.Cond) | ||
case *sb.DeleteBuilder: | ||
appendNamespaceFilter(ctx, &x.Cond) | ||
} | ||
return b.Build() | ||
} | ||
|
||
func appendNamespaceFilter(ctx context.Context, cond *sb.Cond) *sb.Cond { | ||
v := ctx.Value(namespaceCtxKey{}) | ||
if v == nil { | ||
// no namespace in context | ||
return cond | ||
} | ||
|
||
s, ok := v.(string) | ||
if !ok { | ||
// incorrect namespace value in context, ignore it | ||
return cond | ||
} | ||
|
||
if s == "" || s == "-" || s == "<nil>" { | ||
// empty namespace value in context, ignore it | ||
return cond | ||
} | ||
|
||
if shouldIgnoreNamespace(ctx) { | ||
// user-defined force ignore namespace in context | ||
return cond | ||
} | ||
|
||
// append namespace where condition into Cond | ||
cond.E(namespaceColumnName, s) | ||
return cond | ||
} | ||
|
||
// WithNamespace add namespace info into context | ||
func WithNamespace(ctx context.Context, namespace string) context.Context { | ||
return context.WithValue(ctx, namespaceCtxKey{}, namespace) | ||
} | ||
|
||
// IgnnoreNamespace force ormx ignore the namespace info in context, so that Build will not inject namespace filter in sql | ||
func IgnoreNamespace(ctx context.Context) context.Context { | ||
return context.WithValue(ctx, ignoreNamespaceCtxKey{}, true) | ||
} | ||
|
||
func shouldIgnoreNamespace(ctx context.Context) bool { | ||
v := ctx.Value(ignoreNamespaceCtxKey{}) | ||
if v == nil { | ||
return false | ||
} | ||
switch x := v.(type) { | ||
case bool: | ||
return x | ||
case string: | ||
x = strings.ToLower(x) | ||
return x == "1" || x == "true" | ||
case int, int64, uint, uint64, float64, float32: | ||
return x != 0 | ||
} | ||
return false | ||
} | ||
|
||
type namespaceCtxKey struct{} | ||
type ignoreNamespaceCtxKey struct{} | ||
|
||
// WhereFromStruct generate where exprs from data(type of struct), the returned value can be used by builder.Where method | ||
func WhereFromStruct(data any, dst []string) []string { | ||
if data == nil { | ||
return []string{} | ||
} | ||
v := dereferencedValue(reflect.ValueOf(data)) | ||
t := dereferencedType(reflect.TypeOf(data)) | ||
if !v.IsValid() || v.IsZero() { | ||
return []string{} | ||
} | ||
for i := 0; i < v.NumField(); i++ { | ||
field := v.Field(i) | ||
fieldType := t.Field(i) | ||
if field.IsNil() { | ||
continue | ||
} | ||
name, _ := colNameFromTag(fieldType) | ||
if name == "" { | ||
continue | ||
} | ||
fieldValue := dereferencedValue(field).Interface() | ||
dst = appendWhereExpr(dst, name, fieldValue, fieldType.Tag.Get("op")) | ||
} | ||
return dst | ||
} | ||
|
||
// WhereFromStruct generate where exprs from []KV, the returned value can be used by builder.Where method | ||
func WhereFromKVs(filter KVs, dst []string) []string { | ||
if filter == nil { | ||
return []string{} | ||
} | ||
for _, kv := range filter { | ||
colName := kv.Key | ||
fieldValue := kv.Value | ||
dst = appendWhereExpr(dst, colName, fieldValue, kv.Extra) | ||
} | ||
return dst | ||
} | ||
|
||
func WhereFromIDs(idList []int64, dst []string) []string { | ||
c := sb.NewCond() | ||
t := reflect.TypeOf(idList) | ||
if t.Kind() == reflect.Slice { | ||
dst = append(dst, c.In(primaryKey, Any2Slice(idList)...)) | ||
} | ||
return dst | ||
} | ||
|
||
func WhereFromID(id int64, dst []string) []string { | ||
c := sb.NewCond() | ||
t := reflect.TypeOf(id) | ||
if t.Kind() == reflect.Slice { | ||
dst = append(dst, c.E(primaryKey, id)) | ||
} | ||
return dst | ||
} | ||
|
||
func WhereFrom(filter any, dst []string) []string { | ||
c := sb.NewCond() | ||
if kvs, ok := filter.(KVs); ok { | ||
return WhereFromKVs(kvs, dst) | ||
} | ||
t := dereferencedType(reflect.TypeOf(filter)) | ||
if kind := t.Kind(); kind == reflect.Struct { | ||
return WhereFromStruct(filter, dst) | ||
} else if kind == reflect.Slice { | ||
dst = append(dst, c.In(primaryKey, Any2Slice(filter)...)) | ||
} else { | ||
dst = append(dst, c.E(primaryKey, filter)) | ||
} | ||
return dst | ||
} | ||
|
||
func appendWhereExpr(dst []string, column string, value any, op string) []string { | ||
c := &sb.Cond{ | ||
Args: &sb.Args{}, | ||
} | ||
switch op { | ||
case "": | ||
if dereferencedType(reflect.TypeOf(value)).Kind() == reflect.Slice { | ||
dst = append(dst, c.In(column, Any2Slice(value)...)) | ||
} else { | ||
dst = append(dst, c.E(column, value)) | ||
} | ||
case "e": | ||
dst = append(dst, c.E(column, value)) | ||
case "ne": | ||
dst = append(dst, c.NE(column, value)) | ||
case "gt": | ||
dst = append(dst, c.GreaterThan(column, value)) | ||
case "gte": | ||
dst = append(dst, c.GreaterEqualThan(column, value)) | ||
case "lt": | ||
dst = append(dst, c.LessThan(column, value)) | ||
case "lte": | ||
dst = append(dst, c.LessEqualThan(column, value)) | ||
case "in": | ||
if values := Any2Slice(value); len(values) > 0 { | ||
dst = append(dst, c.In(column, Any2Slice(value)...)) | ||
} else { | ||
dst = append(dst, c.IsNull(column)) | ||
} | ||
case "notin": | ||
dst = append(dst, c.NotIn(column, Any2Slice(value)...)) | ||
case "like": | ||
dst = append(dst, c.Like(column, value)) | ||
case "notlike": | ||
dst = append(dst, c.NotLike(column, value)) | ||
} | ||
return dst | ||
} | ||
|
||
// TableName auto recoganize the table name from data, it will auto prepend the tableNamePrefix which can be set by SetTableNamePrefix to the result. | ||
// - having Table() method, it will call d.Table() to get the table name | ||
// - type of struct, it will use the struct name, and snake case it | ||
// - type of string, return the name. | ||
// - type of other, return fmt.Sprintf("%s", d) | ||
func TableName(d interface{}) string { | ||
if d == nil { | ||
return "" | ||
} | ||
S: | ||
t, ok := d.(interface { | ||
Table() string | ||
}) | ||
|
||
if ok { | ||
return t.Table() | ||
} | ||
|
||
var ( | ||
vt = dereferencedElemType(reflect.TypeOf(d)) | ||
name string | ||
) | ||
|
||
switch vt.Kind() { | ||
case reflect.String: | ||
return d.(string) | ||
case reflect.Struct: | ||
structName := vt.Name() | ||
name = sb.SnakeCaseMapper(structName) | ||
case reflect.Slice: | ||
d = reflect.New(vt.Elem()).Interface() | ||
goto S | ||
default: | ||
name = fmt.Sprintf("%s", d) | ||
} | ||
|
||
if strings.HasPrefix(name, tableNamePrefix) { | ||
return name | ||
} | ||
|
||
return tableNamePrefix + name | ||
} | ||
|
||
// ColNamesWithTagOpt will column names from structure data, the type of d must be a struct, otherwise will return []string{}. | ||
// | ||
// ColNamesWithTagOpt will try to filter the filter the struct field which having <tag> specified in StructField.Tag if <tag> is not empty | ||
func ColNamesWithTagOpt(d interface{}, tag string) []string { | ||
vt := reflect.TypeOf(d) | ||
if vt.Kind() == reflect.Ptr { | ||
vt = vt.Elem() | ||
} | ||
if vt.Kind() != reflect.Struct { | ||
return []string{} | ||
} | ||
table := TableName(d) | ||
var cols []string | ||
for i := 0; i < vt.NumField(); i++ { | ||
field := vt.Field(i) | ||
name, after := colNameFromTag(field) | ||
if name == "" { | ||
continue | ||
} | ||
if tag != "" { | ||
if opts := ParseOptionStr(after); opts != nil { | ||
if _, ok := opts[tag]; !ok { | ||
continue | ||
} | ||
} | ||
} | ||
cols = append(cols, table+"."+name) | ||
} | ||
return cols | ||
} | ||
|
||
func colNameFromTag(field reflect.StructField) (string, string) { | ||
if !field.IsExported() { | ||
return "", "" | ||
} | ||
switch field.Type.Kind() { | ||
case reflect.Func, reflect.Chan: | ||
return "", "" | ||
} | ||
name, after, _ := strings.Cut(field.Tag.Get(structTagName), ",") | ||
if name == "-" { | ||
return "", "" | ||
} else if name == "" { | ||
return field.Name, after | ||
} | ||
return name, after | ||
} |
Oops, something went wrong.