beancount-gs/script/bql.go

277 lines
7.0 KiB
Go
Raw Normal View History

2021-11-21 14:37:13 +00:00
package script
import (
2021-11-22 08:47:49 +00:00
"encoding/json"
2021-11-21 14:37:13 +00:00
"fmt"
"log"
2021-11-21 14:37:13 +00:00
"os/exec"
"reflect"
2021-11-22 14:50:10 +00:00
"strconv"
2021-11-21 14:37:13 +00:00
"strings"
"github.com/gin-gonic/gin"
2021-11-21 14:37:13 +00:00
)
type QueryParams struct {
2021-12-02 14:48:45 +00:00
From bool `bql:"From"`
FromYear int `bql:"year ="`
FromMonth int `bql:"month ="`
Where bool `bql:"where"`
2021-11-26 09:12:07 +00:00
Currency string `bql:"currency ="`
2021-11-21 14:37:13 +00:00
Year int `bql:"year ="`
Month int `bql:"month ="`
2021-11-24 10:04:02 +00:00
Tag string `bql:"in tags"`
2021-11-24 09:32:24 +00:00
Account string `bql:"account ="`
2021-11-24 15:45:45 +00:00
AccountLike string `bql:"account ~"`
GroupBy string `bql:"group by"`
OrderBy string `bql:"order by"`
Limit int `bql:"limit"`
2021-11-23 15:33:14 +00:00
Path string
2021-11-21 14:37:13 +00:00
}
2021-11-22 14:50:10 +00:00
func GetQueryParams(c *gin.Context) QueryParams {
var queryParams QueryParams
var hasWhere bool
if c.Query("year") != "" {
val, err := strconv.Atoi(c.Query("year"))
if err == nil {
queryParams.Year = val
hasWhere = true
}
}
if c.Query("month") != "" {
val, err := strconv.Atoi(c.Query("month"))
if err == nil {
queryParams.Month = val
hasWhere = true
}
}
2021-11-24 10:04:02 +00:00
if c.Query("tag") != "" {
queryParams.Tag = c.Query("tag")
hasWhere = true
}
2021-11-22 14:50:10 +00:00
if c.Query("type") != "" {
2021-11-24 15:45:45 +00:00
queryParams.AccountLike = c.Query("type")
2021-11-22 14:50:10 +00:00
hasWhere = true
}
2021-11-24 09:32:24 +00:00
if c.Query("account") != "" {
queryParams.Account = c.Query("account")
queryParams.Limit = 100
hasWhere = true
}
2021-11-22 14:50:10 +00:00
queryParams.Where = hasWhere
2021-11-23 15:33:14 +00:00
if c.Query("path") != "" {
queryParams.Path = c.Query("path")
}
2021-11-22 14:50:10 +00:00
return queryParams
}
2021-11-22 08:47:49 +00:00
func BQLQueryOne(ledgerConfig *Config, queryParams *QueryParams, queryResultPtr interface{}) error {
assertQueryResultIsPointer(queryResultPtr)
2021-11-22 14:50:10 +00:00
output, err := bqlRawQuery(ledgerConfig, "", queryParams, queryResultPtr)
2021-11-22 08:47:49 +00:00
if err != nil {
return err
}
err = parseResult(output, queryResultPtr, true)
if err != nil {
return err
}
return nil
}
func BQLQueryList(ledgerConfig *Config, queryParams *QueryParams, queryResultPtr interface{}) error {
assertQueryResultIsPointer(queryResultPtr)
2021-11-22 14:50:10 +00:00
output, err := bqlRawQuery(ledgerConfig, "", queryParams, queryResultPtr)
2021-11-22 08:47:49 +00:00
if err != nil {
return err
}
err = parseResult(output, queryResultPtr, false)
if err != nil {
return err
}
return nil
}
2021-11-22 14:50:10 +00:00
func BQLQueryListByCustomSelect(ledgerConfig *Config, selectBql string, queryParams *QueryParams, queryResultPtr interface{}) error {
assertQueryResultIsPointer(queryResultPtr)
output, err := bqlRawQuery(ledgerConfig, selectBql, queryParams, queryResultPtr)
if err != nil {
return err
2021-11-22 08:47:49 +00:00
}
log.Println(output)
2021-11-22 14:50:10 +00:00
err = parseResult(output, queryResultPtr, false)
if err != nil {
return err
}
return nil
}
func bqlRawQuery(ledgerConfig *Config, selectBql string, queryParamsPtr *QueryParams, queryResultPtr interface{}) (string, error) {
2021-11-25 07:41:28 +00:00
var bql string
2021-11-22 14:50:10 +00:00
if selectBql == "" {
2021-11-25 07:41:28 +00:00
bql = "select"
2021-11-22 14:50:10 +00:00
queryResultPtrType := reflect.TypeOf(queryResultPtr)
queryResultType := queryResultPtrType.Elem()
2021-11-22 08:47:49 +00:00
2021-11-22 14:50:10 +00:00
if queryResultType.Kind() == reflect.Slice {
queryResultType = queryResultType.Elem()
}
for i := 0; i < queryResultType.NumField(); i++ {
typeField := queryResultType.Field(i)
// 字段的 tag 不带 bql 的不进行拼接
b := typeField.Tag.Get("bql")
if b != "" {
if strings.Contains(b, "distinct") {
b = strings.ReplaceAll(b, "distinct", "")
bql = fmt.Sprintf("%s distinct '\\', %s, ", bql, b)
} else {
bql = fmt.Sprintf("%s '\\', %s, ", bql, typeField.Tag.Get("bql"))
}
2021-11-21 14:37:13 +00:00
}
2021-11-22 08:47:49 +00:00
}
2021-11-22 14:50:10 +00:00
bql += " '\\'"
} else {
bql = selectBql
2021-11-22 08:47:49 +00:00
}
2021-11-22 14:50:10 +00:00
2021-11-22 08:47:49 +00:00
// 查询条件不为空时,拼接查询条件
if queryParamsPtr != nil {
queryParamsType := reflect.TypeOf(queryParamsPtr).Elem()
queryParamsValue := reflect.ValueOf(queryParamsPtr).Elem()
for i := 0; i < queryParamsType.NumField(); i++ {
typeField := queryParamsType.Field(i)
valueField := queryParamsValue.Field(i)
switch valueField.Kind() {
case reflect.String:
val := valueField.String()
if val != "" {
2021-11-24 15:45:45 +00:00
if typeField.Name == "OrderBy" || typeField.Name == "GroupBy" {
// 去除上一个条件后缀的 AND 关键字
bql = strings.Trim(bql, " AND")
bql = fmt.Sprintf("%s %s %s", bql, typeField.Tag.Get("bql"), val)
2021-11-24 10:04:02 +00:00
} else if typeField.Name == "Tag" {
bql = fmt.Sprintf("%s '%s' %s", bql, strings.Trim(val, " "), typeField.Tag.Get("bql"))
} else {
bql = fmt.Sprintf("%s %s '%s' AND", bql, typeField.Tag.Get("bql"), val)
}
2021-11-22 08:47:49 +00:00
}
break
case reflect.Int:
val := valueField.Int()
if val != 0 {
bql = fmt.Sprintf("%s %s %d AND", bql, typeField.Tag.Get("bql"), val)
}
break
case reflect.Bool:
val := valueField.Bool()
2021-12-02 14:48:45 +00:00
// where 前的 from 可能会带有 and
if typeField.Name == "Where" {
bql = strings.Trim(bql, " AND")
}
if val {
bql = fmt.Sprintf("%s %s ", bql, typeField.Tag.Get("bql"))
}
break
2021-11-22 08:47:49 +00:00
}
}
bql = strings.TrimRight(bql, " AND")
}
return queryByBQL(ledgerConfig, bql)
}
func parseResult(output string, queryResultPtr interface{}, selectOne bool) error {
queryResultPtrType := reflect.TypeOf(queryResultPtr)
queryResultType := queryResultPtrType.Elem()
if queryResultType.Kind() == reflect.Slice {
queryResultType = queryResultType.Elem()
}
lines := strings.Split(output, "\n")[2:]
if selectOne && len(lines) >= 3 {
lines = lines[2:3]
}
l := make([]map[string]interface{}, 0)
for _, line := range lines {
if line != "" {
values := strings.Split(line, "\\")
// 去除 '\' 分割带来的空字符串
values = values[1 : len(values)-1]
temp := make(map[string]interface{})
for i, val := range values {
field := queryResultType.Field(i)
jsonName := field.Tag.Get("json")
if jsonName == "" {
jsonName = field.Name
}
switch field.Type.Kind() {
2021-11-26 09:12:07 +00:00
case reflect.Int, reflect.Int32:
2021-11-25 07:41:28 +00:00
i, err := strconv.Atoi(strings.Trim(val, " "))
if err != nil {
panic(err)
}
temp[jsonName] = i
break
2021-11-22 14:50:10 +00:00
// decimal
case reflect.String, reflect.Struct:
v := strings.Trim(val, " ")
if v != "" {
temp[jsonName] = v
}
2021-11-22 08:47:49 +00:00
break
case reflect.Array, reflect.Slice:
// 去除空格
strArray := strings.Split(val, ",")
notBlanks := make([]string, 0)
for _, s := range strArray {
if strings.Trim(s, " ") != "" {
notBlanks = append(notBlanks, s)
}
}
temp[jsonName] = notBlanks
break
default:
panic("Unsupported field type")
}
2021-11-21 14:37:13 +00:00
}
2021-11-22 08:47:49 +00:00
l = append(l, temp)
2021-11-21 14:37:13 +00:00
}
}
2021-11-22 08:47:49 +00:00
var jsonBytes []byte
var err error
if selectOne {
jsonBytes, err = json.Marshal(l[0])
} else {
jsonBytes, err = json.Marshal(l)
}
if err != nil {
return err
}
err = json.Unmarshal(jsonBytes, queryResultPtr)
2021-11-21 14:37:13 +00:00
if err != nil {
return err
}
return nil
}
func queryByBQL(ledgerConfig *Config, bql string) (string, error) {
beanFilePath := ledgerConfig.DataPath + "/index.bean"
LogInfo(ledgerConfig.Mail, bql)
cmd := exec.Command("bean-query", beanFilePath, bql)
output, err := cmd.Output()
if err != nil {
return "", err
}
return string(output), nil
}
2021-11-22 08:47:49 +00:00
func assertQueryResultIsPointer(queryResult interface{}) {
k := reflect.TypeOf(queryResult).Kind()
if k != reflect.Ptr {
panic("QueryResult type must be pointer, it's " + k.String())
}
}