MarketingSystemDataExportTool/server/internal/exporter/sqlbuilder.go

690 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package exporter
import (
"encoding/json"
"errors"
"fmt"
"server/internal/schema"
"strconv"
"strings"
"time"
)
type BuildRequest struct {
MainTable string
Datasource string
Fields []string // table.field
Filters map[string]interface{}
}
func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{}, error) {
if req.MainTable != "order" && req.MainTable != "order_info" {
return "", nil, errors.New("unsupported main table")
}
sch := schema.Get(req.Datasource, req.MainTable)
if req.Datasource == "marketing" && req.MainTable == "order" {
if v, ok := req.Filters["create_time_between"]; ok {
switch t := v.(type) {
case []interface{}:
if len(t) != 2 {
return "", nil, errors.New("create_time_between 需要两个时间值")
}
case []string:
if len(t) != 2 {
return "", nil, errors.New("create_time_between 需要两个时间值")
}
default:
return "", nil, errors.New("create_time_between 格式错误")
}
} else {
return "", nil, errors.New("缺少时间过滤:必须提供 create_time_between")
}
}
cols := []string{}
need := map[string]bool{}
for _, tf := range req.Fields {
// normalize YMT physical names saved previously to logical names
if req.Datasource == "ymt" && strings.HasPrefix(tf, "order_info.") {
tf = strings.Replace(tf, "order_info.", "order.", 1)
}
if whitelist != nil && !whitelist[tf] {
continue
}
parts := strings.Split(tf, ".")
if len(parts) != 2 {
return "", nil, errors.New("invalid field format")
}
t, f := parts[0], parts[1]
if req.Datasource == "marketing" && t == "order_voucher" && f == "channel_batch_no" {
f = "channel_activity_id"
}
if req.Datasource == "ymt" && t == "order_voucher" && f == "channel_activity_id" {
f = "channel_batch_no"
}
need[t] = true
mt := sch.TableName(t)
mf, _ := sch.MapField(t, f)
if req.Datasource == "marketing" && t == "order" && req.MainTable == "order" {
if f == "status" {
cols = append(cols, "CASE `order`.status WHEN 0 THEN '待充值' WHEN 1 THEN '充值中' WHEN 2 THEN '已完成' WHEN 3 THEN '充值失败' WHEN 4 THEN '已取消' WHEN 5 THEN '已过期' WHEN 6 THEN '待支付' END AS `order.status`")
continue
}
if f == "type" {
cols = append(cols, "CASE `order`.type WHEN 1 THEN '直充卡密' WHEN 2 THEN '立减金' WHEN 3 THEN '红包' ELSE '' END AS `order.type`")
continue
}
if f == "pay_type" {
cols = append(cols, "CASE `order`.pay_type WHEN 1 THEN '支付宝' WHEN 5 THEN '微信' ELSE '' END AS `order.pay_type`")
continue
}
if f == "pay_status" {
cols = append(cols, "CASE `order`.pay_status WHEN 1 THEN '待支付' WHEN 2 THEN '已支付' WHEN 3 THEN '已退款' ELSE '' END AS `order.pay_status`")
continue
}
if req.Datasource == "marketing" && f == "card_code" {
cols = append(cols, "CASE WHEN LENGTH(`order`.card_code) > 10 THEN CONCAT(SUBSTRING(`order`.card_code,1,6),'****',SUBSTRING(`order`.card_code, LENGTH(`order`.card_code)-3, 4)) ELSE `order`.card_code END AS `order.card_code`")
continue
}
}
if req.Datasource == "ymt" && t == "order" {
if f == "type" {
cols = append(cols, "CASE `"+mt+"`.type WHEN 1 THEN '红包订单' WHEN 2 THEN '直充卡密订单' WHEN 3 THEN '立减金订单' ELSE '' END AS `order.type`")
continue
}
if f == "recharge_suc_time" {
// 仅在充值成功状态下展示充值成功时间,其余状态展示为空
cols = append(cols, "CASE WHEN `"+mt+"`.status = 3 THEN `"+mt+"`.recharge_suc_time ELSE NULL END AS `order.recharge_suc_time`")
continue
}
if f == "status" {
cols = append(cols, "CASE `"+mt+"`.status WHEN 1 THEN '待充值' WHEN 2 THEN '充值中' WHEN 3 THEN '充值成功' WHEN 4 THEN '充值失败' WHEN 5 THEN '已过期' WHEN 6 THEN '已作废' WHEN 7 THEN '已核销' WHEN 8 THEN '核销失败' WHEN 9 THEN '订单重置' WHEN 10 THEN '卡单' ELSE '' END AS `order.status`")
continue
}
if f == "pay_status" {
cols = append(cols, "CASE `"+mt+"`.pay_status WHEN 1 THEN '待支付' WHEN 2 THEN '支付中' WHEN 3 THEN '已支付' WHEN 4 THEN '取消支付' WHEN 5 THEN '退款中' WHEN 6 THEN '退款成功' ELSE '' END AS `order.pay_status`")
continue
}
if f == "is_retry" {
cols = append(cols, "CASE `"+mt+"`.is_retry WHEN 0 THEN '可以失败重试' WHEN 1 THEN '可以失败重试' WHEN 2 THEN '不可以失败重试' ELSE '' END AS `order.is_retry`")
continue
}
if f == "supplier_name" {
need["supplier"] = true
cols = append(cols, "`supplier`.name AS `order.supplier_name`")
continue
}
if f == "is_inner" {
cols = append(cols, "CASE `"+mt+"`.is_inner WHEN 1 THEN '内部供应商' ELSE '外部供应商' END AS `order.is_inner`")
continue
}
}
if req.Datasource == "ymt" && t == "activity" {
if f == "settlement_type" {
cols = append(cols, "CASE `"+mt+"`.settlement_type WHEN 1 THEN '发放结算' WHEN 2 THEN '打开结算' WHEN 3 THEN '领用结算' WHEN 4 THEN '核销结算' ELSE '' END AS `activity.settlement_type`")
continue
}
}
if t == "merchant" {
if f == "third_party" {
cols = append(cols, "CASE `"+mt+"`.third_party WHEN 1 THEN '外部供应商' WHEN 2 THEN '内部供应商' ELSE '' END AS `merchant.third_party`")
continue
}
}
// Generic mapping for order.is_retry across datasources
if t == "order" && f == "is_retry" {
cols = append(cols, "CASE `"+mt+"`.is_retry WHEN 0 THEN '可以失败重试' WHEN 1 THEN '可以失败重试' WHEN 2 THEN '不可以失败重试' ELSE '' END AS `order.is_retry`")
continue
}
// Generic mapping for order.is_inner across datasources
if t == "order" && f == "is_inner" {
cols = append(cols, "CASE `"+mt+"`.is_inner WHEN 1 THEN '内部供应商' ELSE '外部供应商' END AS `order.is_inner`")
continue
}
if req.Datasource == "ymt" && t == "order_digit" {
if f == "order_type" {
cols = append(cols, "CASE `"+mt+"`.order_type WHEN 1 THEN '直充' WHEN 2 THEN '卡密' ELSE '' END AS `order_digit.order_type`")
continue
}
if f == "sms_channel" {
// 短信渠道枚举1=官方2=专票
cols = append(cols, "CASE `"+mt+"`.sms_channel WHEN 1 THEN '官方' WHEN 2 THEN '专票' ELSE '' END AS `order_digit.sms_channel`")
continue
}
}
if t == "order_cash" && f == "receive_status" {
cols = append(cols, "CASE `order_cash`.receive_status WHEN 0 THEN '待领取' WHEN 1 THEN '领取中' WHEN 2 THEN '领取成功' WHEN 3 THEN '领取失败' ELSE '' END AS `order_cash.receive_status`")
continue
}
// YMT 的 order_cash 表无 is_confirm 字段,输出占位常量
if req.Datasource == "ymt" && t == "order_cash" && f == "is_confirm" {
cols = append(cols, "0 AS `order_cash.is_confirm`")
continue
}
if t == "order_cash" && f == "channel" {
cols = append(cols, "CASE `order_cash`.channel WHEN 1 THEN '支付宝' WHEN 2 THEN '微信' WHEN 3 THEN '云闪付' ELSE '' END AS `order_cash.channel`")
continue
}
if t == "order_voucher" && f == "channel" {
cols = append(cols, "CASE `order_voucher`.channel WHEN 1 THEN '支付宝' WHEN 2 THEN '微信' WHEN 3 THEN '云闪付' ELSE '' END AS `order_voucher.channel`")
continue
}
if req.Datasource == "ymt" && t == "order_voucher" && f == "status" {
cols = append(cols, "CASE `order_voucher`.status WHEN 1 THEN '待发放' WHEN 2 THEN '发放中' WHEN 3 THEN '发放失败' WHEN 4 THEN '待核销' WHEN 5 THEN '已核销' WHEN 6 THEN '已过期' WHEN 7 THEN '已退款' ELSE '' END AS `order_voucher.status`")
continue
}
if t == "order_voucher" && f == "status" {
cols = append(cols, "CASE `order_voucher`.status WHEN 1 THEN '可用' WHEN 2 THEN '已实扣' WHEN 3 THEN '已过期' WHEN 4 THEN '已退款' WHEN 5 THEN '领取失败' WHEN 6 THEN '发放中' WHEN 7 THEN '部分退款' WHEN 8 THEN '已退回' WHEN 9 THEN '发放失败' ELSE '' END AS `order_voucher.status`")
continue
}
if t == "order_voucher" && f == "receive_mode" {
cols = append(cols, "CASE `order_voucher`.receive_mode WHEN 1 THEN '渠道授权用户id' WHEN 2 THEN '手机号或邮箱' ELSE '' END AS `order_voucher.receive_mode`")
continue
}
if t == "order_voucher" && f == "out_biz_no" {
cols = append(cols, "'' AS `order_voucher.out_biz_no`")
continue
}
// Fallback for YMT tables that are not joined in schema: voucher, voucher_batch, merchant_key_send
if req.Datasource == "ymt" && (t == "voucher" || t == "voucher_batch" || t == "merchant_key_send") {
cols = append(cols, "'' AS `"+t+"."+f+"`")
continue
}
cols = append(cols, "`"+mt+"`."+escape(mf)+" AS `"+t+"."+f+"`")
}
if len(cols) == 0 {
return "", nil, errors.New("no fields")
}
sb := strings.Builder{}
baseCols := strings.Join(cols, ",")
sb.WriteString("SELECT ")
sb.WriteString(baseCols)
sb.WriteString(" FROM `" + sch.TableName(req.MainTable) + "`")
args := []interface{}{}
where := []string{}
// collect need from filters referencing related tables
if _, ok := req.Filters["order_cash_cash_activity_id_eq"]; ok {
need["order_cash"] = true
}
if _, ok := req.Filters["order_voucher_channel_activity_id_eq"]; ok {
need["order_voucher"] = true
}
if _, ok := req.Filters["voucher_batch_channel_activity_id_eq"]; ok {
need["voucher_batch"] = true
need["voucher"] = true
need["order_voucher"] = true
}
if _, ok := req.Filters["merchant_out_biz_no_eq"]; ok {
need["merchant_key_send"] = true
}
// Handle creator_in and merchant_id_in with OR logic if both exist
var creatorArgs []interface{}
hasCreator := false
if v, ok := req.Filters["creator_in"]; ok {
switch t := v.(type) {
case []interface{}:
creatorArgs = t
case []int:
for _, x := range t {
creatorArgs = append(creatorArgs, x)
}
case []string:
for _, x := range t {
creatorArgs = append(creatorArgs, x)
}
}
if len(creatorArgs) > 0 {
hasCreator = true
}
}
var merchantArgs []interface{}
hasMerchant := false
if v, ok := req.Filters["merchant_id_in"]; ok {
switch t := v.(type) {
case []interface{}:
merchantArgs = t
case []int:
for _, x := range t {
merchantArgs = append(merchantArgs, x)
}
case []string:
for _, x := range t {
merchantArgs = append(merchantArgs, x)
}
}
if len(merchantArgs) > 0 {
hasMerchant = true
}
}
// Apply the logic: if both present, use OR. Else use individual.
if hasCreator && hasMerchant {
cTbl, cCol, cOk := sch.FilterColumn("creator_in")
mTbl, mCol, mOk := sch.FilterColumn("merchant_id_in")
if cOk && mOk {
cPh := strings.Repeat("?,", len(creatorArgs))
cPh = strings.TrimSuffix(cPh, ",")
mPh := strings.Repeat("?,", len(merchantArgs))
mPh = strings.TrimSuffix(mPh, ",")
where = append(where, fmt.Sprintf("(`%s`.%s IN (%s) OR `%s`.%s IN (%s))",
sch.TableName(cTbl), escape(cCol), cPh,
sch.TableName(mTbl), escape(mCol), mPh))
args = append(args, creatorArgs...)
args = append(args, merchantArgs...)
} else if cOk {
// Fallback: only creator valid (e.g. marketing system)
ph := strings.Repeat("?,", len(creatorArgs))
ph = strings.TrimSuffix(ph, ",")
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(cTbl), escape(cCol), ph))
args = append(args, creatorArgs...)
}
} else if hasCreator {
if tbl, col, ok := sch.FilterColumn("creator_in"); ok {
ph := strings.Repeat("?,", len(creatorArgs))
ph = strings.TrimSuffix(ph, ",")
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(tbl), escape(col), ph))
args = append(args, creatorArgs...)
}
}
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")
}
if tbl, col, ok := sch.FilterColumn("create_time_between"); ok {
where = append(where, fmt.Sprintf("`%s`.%s BETWEEN ? AND ?", sch.TableName(tbl), escape(col)))
}
args = append(args, arr[0], arr[1])
}
var tv int
if v, ok := req.Filters["type_eq"]; ok {
switch t := v.(type) {
case float64:
tv = int(t)
case int:
tv = t
case string:
// simple digits parsing and label mapping
_s := strings.TrimSpace(t)
for i := 0; i < len(_s); i++ {
c := _s[i]
if c < '0' || c > '9' {
continue
}
tv = tv*10 + int(c-'0')
}
if tv == 0 {
if req.Datasource == "ymt" {
if _s == "红包订单" {
tv = 1
}
if _s == "直充卡密订单" {
tv = 2
}
if _s == "立减金订单" {
tv = 3
}
} else {
if _s == "直充卡密" {
tv = 1
}
if _s == "立减金" {
tv = 2
}
if _s == "红包" {
tv = 3
}
}
}
}
if tv == 1 || tv == 2 || tv == 3 {
if tbl, col, ok := sch.FilterColumn("type_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, tv)
}
}
if v, ok := req.Filters["out_trade_no_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("out_trade_no_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["account_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("account_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["plan_id_eq"]; ok {
s := toString(v)
if s != "" && s != "0" {
if tbl, col, ok := sch.FilterColumn("plan_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["key_batch_id_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("key_batch_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["product_id_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("product_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["reseller_id_eq"]; ok {
// If merchant_id_in is present, it handles the merchant_id logic (via OR condition),
if _, hasIn := req.Filters["merchant_id_in"]; !hasIn {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("reseller_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
}
if v, ok := req.Filters["code_batch_id_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("code_batch_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["order_cash_cash_activity_id_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("order_cash_cash_activity_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["order_voucher_channel_activity_id_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("order_voucher_channel_activity_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
if v, ok := req.Filters["voucher_batch_channel_activity_id_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("voucher_batch_channel_activity_id_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
args = append(args, s)
}
}
}
if v, ok := req.Filters["merchant_out_biz_no_eq"]; ok {
s := toString(v)
if s != "" {
if tbl, col, ok := sch.FilterColumn("merchant_out_biz_no_eq"); ok {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
}
args = append(args, s)
}
}
// append necessary joins after collecting filter-driven needs
joins := sch.BuildJoins(need, req.MainTable)
for _, j := range joins {
sb.WriteString(j)
}
if len(where) > 0 {
sb.WriteString(" WHERE ")
sb.WriteString(strings.Join(where, " AND "))
}
needDedupe := req.Datasource == "marketing" && req.MainTable == "order" && (need["order_voucher"] || need["voucher"] || need["voucher_batch"] || need["key_batch"] || need["code_batch"])
if needDedupe {
// Extract alias names in order
aliases := make([]string, 0, len(cols))
sources := make([]string, 0, len(cols))
for _, c := range cols {
pos := strings.LastIndex(c, " AS `")
if pos < 0 {
continue
}
alias := c[pos+5:]
if len(alias) == 0 {
continue
}
if alias[len(alias)-1] == '`' {
alias = alias[:len(alias)-1]
}
aliases = append(aliases, alias)
parts := strings.Split(alias, ".")
src := ""
if len(parts) >= 1 {
src = parts[0]
}
sources = append(sources, src)
}
mt := sch.TableName(req.MainTable)
pkCol, _ := sch.MapField(req.MainTable, "order_number")
var out strings.Builder
out.WriteString("SELECT ")
for i := range aliases {
if i > 0 {
out.WriteString(",")
}
alias := aliases[i]
src := sources[i]
if src == "order" {
out.WriteString("sub.`" + alias + "`")
} else {
out.WriteString("MIN(sub.`" + alias + "`) AS `" + alias + "`")
}
}
out.WriteString(" FROM (")
out.WriteString(sb.String())
out.WriteString(") AS sub GROUP BY sub.`" + mt + "." + pkCol + "`")
return out.String(), args, nil
}
return sb.String(), args, nil
}
func escape(s string) string {
if s == "key" {
return "`key`"
}
if s == "index" {
return "`index`"
}
return s
}
func toString(v interface{}) string {
switch t := v.(type) {
case []byte:
return string(t)
case string:
return t
case int64:
return strconv.FormatInt(t, 10)
case int32:
return strconv.FormatInt(int64(t), 10)
case int:
return strconv.Itoa(t)
case uint64:
return strconv.FormatUint(t, 10)
case uint32:
return strconv.FormatUint(uint64(t), 10)
case uint:
return strconv.FormatUint(uint64(t), 10)
case float64:
// 对于整数部分,使用整数格式;对于小数部分,保留必要精度
if t == float64(int64(t)) {
return strconv.FormatInt(int64(t), 10)
}
return strconv.FormatFloat(t, 'f', -1, 64)
case float32:
// 对于整数部分,使用整数格式;对于小数部分,保留必要精度
if t == float32(int64(t)) {
return strconv.FormatInt(int64(t), 10)
}
return strconv.FormatFloat(float64(t), 'f', -1, 32)
case time.Time:
return t.Format("2006-01-02 15:04:05")
case nil:
return ""
default:
return ""
}
}
// BuildCountSQL: minimal COUNT for filters-only joins, counting distinct main PK to avoid 1:N duplication
func BuildCountSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{}, error) {
if req.MainTable != "order" && req.MainTable != "order_info" {
return "", nil, errors.New("unsupported main table")
}
sch := schema.Get(req.Datasource, req.MainTable)
mt := sch.TableName(req.MainTable)
pkCol, _ := sch.MapField(req.MainTable, "order_number")
args := []interface{}{}
where := []string{}
need := map[string]bool{}
// mark joins only needed by filters
for k, _ := range req.Filters {
if tbl, _, ok := sch.FilterColumn(k); ok {
need[tbl] = true
}
}
// Handle creator_in and merchant_id_in with OR logic if both exist
var creatorArgs []interface{}
hasCreator := false
if v, ok := req.Filters["creator_in"]; ok {
switch t := v.(type) {
case []interface{}:
creatorArgs = t
case []int:
for _, x := range t {
creatorArgs = append(creatorArgs, x)
}
case []string:
for _, x := range t {
creatorArgs = append(creatorArgs, x)
}
}
if len(creatorArgs) > 0 {
hasCreator = true
}
}
var merchantArgs []interface{}
hasMerchant := false
if v, ok := req.Filters["merchant_id_in"]; ok {
switch t := v.(type) {
case []interface{}:
merchantArgs = t
case []int:
for _, x := range t {
merchantArgs = append(merchantArgs, x)
}
case []string:
for _, x := range t {
merchantArgs = append(merchantArgs, x)
}
}
if len(merchantArgs) > 0 {
hasMerchant = true
}
}
if hasCreator && hasMerchant {
cTbl, cCol, cOk := sch.FilterColumn("creator_in")
mTbl, mCol, mOk := sch.FilterColumn("merchant_id_in")
if cOk && mOk {
cPh := strings.Repeat("?,", len(creatorArgs))
cPh = strings.TrimSuffix(cPh, ",")
mPh := strings.Repeat("?,", len(merchantArgs))
mPh = strings.TrimSuffix(mPh, ",")
where = append(where, fmt.Sprintf("(`%s`.%s IN (%s) OR `%s`.%s IN (%s))",
sch.TableName(cTbl), escape(cCol), cPh,
sch.TableName(mTbl), escape(mCol), mPh))
args = append(args, creatorArgs...)
args = append(args, merchantArgs...)
} else if cOk {
ph := strings.Repeat("?,", len(creatorArgs))
ph = strings.TrimSuffix(ph, ",")
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(cTbl), escape(cCol), ph))
args = append(args, creatorArgs...)
}
} else if hasCreator {
if tbl, col, ok := sch.FilterColumn("creator_in"); ok {
ph := strings.Repeat("?,", len(creatorArgs))
ph = strings.TrimSuffix(ph, ",")
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(tbl), escape(col), ph))
args = append(args, creatorArgs...)
}
}
// build WHERE from other filters
for k, v := range req.Filters {
if k == "creator_in" || k == "merchant_id_in" {
continue
}
if k == "reseller_id_eq" {
if _, has := req.Filters["merchant_id_in"]; has {
continue
}
}
if k == "plan_id_eq" && toString(v) == "0" {
continue
}
if tbl, col, ok := sch.FilterColumn(k); ok {
switch k {
case "create_time_between":
var arr []interface{}
b, _ := json.Marshal(v)
_ = json.Unmarshal(b, &arr)
if len(arr) == 2 {
where = append(where, fmt.Sprintf("`%s`.%s BETWEEN ? AND ?", sch.TableName(tbl), escape(col)))
args = append(args, arr[0], arr[1])
}
default:
s := toString(v)
if s != "" {
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
args = append(args, s)
}
}
}
}
sb := strings.Builder{}
sb.WriteString("SELECT COUNT(DISTINCT `" + mt + "`." + pkCol + ") FROM `" + mt + "`")
for _, j := range sch.BuildJoins(need, req.MainTable) {
sb.WriteString(j)
}
if len(where) > 0 {
sb.WriteString(" WHERE ")
sb.WriteString(strings.Join(where, " AND "))
}
return sb.String(), args, nil
}