MarketingSystemDataTool/server/internal/exporter/sqlbuilder.go

115 lines
4.2 KiB
Go

package exporter
import (
"encoding/json"
"errors"
"strings"
)
type BuildRequest struct {
MainTable string
Fields []string // table.field
Filters map[string]interface{}
}
func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{}, error) {
if req.MainTable != "order" {
return "", nil, errors.New("unsupported main table")
}
cols := []string{}
need := map[string]bool{}
for _, tf := range req.Fields {
if !whitelist[tf] {
return "", nil, errors.New("field not allowed")
}
parts := strings.Split(tf, ".")
if len(parts) != 2 { return "", nil, errors.New("invalid field format") }
t, f := parts[0], parts[1]
need[t] = true
if t == "order" {
cols = append(cols, "`order`."+escape(f))
} else {
cols = append(cols, "`"+t+"`."+escape(f))
}
}
if len(cols) == 0 {
return "", nil, errors.New("no fields")
}
sb := strings.Builder{}
sb.WriteString("SELECT ")
sb.WriteString(strings.Join(cols, ","))
sb.WriteString(" FROM `order`")
// JOINs based on need
// order_detail
if need["order_detail"] { sb.WriteString(" LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number") }
// order_cash
if need["order_cash"] { sb.WriteString(" LEFT JOIN `order_cash` ON `order_cash`.order_number = `order`.order_number") }
// order_voucher
if need["order_voucher"] { sb.WriteString(" LEFT JOIN `order_voucher` ON `order_voucher`.order_number = `order`.order_number") }
// plan
if need["plan"] || need["key_batch"] { sb.WriteString(" LEFT JOIN `plan` ON `plan`.id = `order`.plan_id") }
// key_batch depends on plan
if need["key_batch"] { sb.WriteString(" LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id") }
// code_batch depends on key_batch
if need["code_batch"] { sb.WriteString(" LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id") }
// voucher depends on order_voucher
if need["voucher"] { sb.WriteString(" LEFT JOIN `voucher` ON `voucher`.channel_activity_id = `order_voucher`.channel_activity_id") }
// voucher_batch depends on voucher
if need["voucher_batch"] { sb.WriteString(" LEFT JOIN `voucher_batch` ON `voucher_batch`.voucher_id = `voucher`.id") }
// merchant_key_send depends on order.key
if need["merchant_key_send"] { sb.WriteString(" LEFT JOIN `merchant_key_send` ON `order`." + escape("key") + " = `merchant_key_send`.key") }
args := []interface{}{}
where := []string{}
if v, ok := req.Filters["creator_in"]; ok {
ids := []interface{}{}
switch t := v.(type) {
case []interface{}:
ids = t
case []int:
for _, x := range t { ids = append(ids, x) }
case []string:
for _, x := range t { ids = append(ids, x) }
}
if len(ids) == 0 { return "", nil, errors.New("creator_in required") }
ph := strings.Repeat("?,", len(ids))
ph = strings.TrimSuffix(ph, ",")
where = append(where, "`order`.creator IN ("+ph+")")
args = append(args, ids...)
}
if v, ok := req.Filters["create_time_between"]; ok {
var arr []interface{}
b, _ := json.Marshal(v)
json.Unmarshal(b, &arr)
if len(arr) != 2 { return "", nil, errors.New("create_time_between requires 2 values") }
where = append(where, "`order`.create_time BETWEEN ? AND ?")
args = append(args, arr[0], arr[1])
}
if v, ok := req.Filters["type_eq"]; ok {
var tv int
switch t := v.(type) {
case float64:
tv = int(t)
case int:
tv = t
case string:
// simple digits parsing
for i := 0; i < len(t); i++ { c := t[i]; if c<'0'||c>'9' { continue }; tv = tv*10 + int(c-'0') }
}
if tv == 1 || tv == 2 || tv == 3 {
where = append(where, "`order`.type = ?")
args = append(args, tv)
}
}
if len(where) > 0 {
sb.WriteString(" WHERE ")
sb.WriteString(strings.Join(where, " AND "))
}
return sb.String(), args, nil
}
func escape(s string) string {
if s == "key" { return "`key`" }
return s
}