作者 yangfu

feat: ast expr version 2,byte to rune

@@ -11,6 +11,7 @@ require ( @@ -11,6 +11,7 @@ require (
11 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect 11 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
12 github.com/fatih/structs v1.1.0 // indirect 12 github.com/fatih/structs v1.1.0 // indirect
13 github.com/gavv/httpexpect v2.0.0+incompatible 13 github.com/gavv/httpexpect v2.0.0+incompatible
  14 + github.com/go-gota/gota v0.12.0
14 github.com/go-pg/pg/v10 v10.10.6 15 github.com/go-pg/pg/v10 v10.10.6
15 github.com/go-redis/redis v6.15.9+incompatible 16 github.com/go-redis/redis v6.15.9+incompatible
16 github.com/google/go-querystring v1.1.0 // indirect 17 github.com/google/go-querystring v1.1.0 // indirect
@@ -29,6 +30,7 @@ require ( @@ -29,6 +30,7 @@ require (
29 github.com/stretchr/testify v1.7.1 30 github.com/stretchr/testify v1.7.1
30 github.com/valyala/fasthttp v1.38.0 // indirect 31 github.com/valyala/fasthttp v1.38.0 // indirect
31 github.com/xeipuuv/gojsonschema v1.2.0 // indirect 32 github.com/xeipuuv/gojsonschema v1.2.0 // indirect
  33 + github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
32 github.com/xuri/excelize/v2 v2.6.0 34 github.com/xuri/excelize/v2 v2.6.0
33 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect 35 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
34 github.com/yudai/gojsondiff v1.0.0 // indirect 36 github.com/yudai/gojsondiff v1.0.0 // indirect
@@ -57,9 +57,10 @@ func init() { @@ -57,9 +57,10 @@ func init() {
57 "rounddown": {-1, defNone}, 57 "rounddown": {-1, defNone},
58 "roundup": {-1, defNone}, 58 "roundup": {-1, defNone},
59 "count": {-1, defNone}, 59 "count": {-1, defNone},
60 - "countif": {-1, defNone}, 60 + "countifs": {-1, defNone},
61 //"&": {-1, defNone}, 61 //"&": {-1, defNone},
62 "concat": {-1, defNone}, 62 "concat": {-1, defNone},
  63 + "sumifs": {-1, defNone},
63 } 64 }
64 } 65 }
65 66
@@ -35,6 +35,7 @@ func NewFieldExprAST(val string) FieldExprAST { @@ -35,6 +35,7 @@ func NewFieldExprAST(val string) FieldExprAST {
35 Str: val, 35 Str: val,
36 Field: &domain.TableField{ 36 Field: &domain.TableField{
37 FieldName: filed, 37 FieldName: filed,
  38 + FieldSqlName: filed,
38 TableName: table, 39 TableName: table,
39 }, 40 },
40 } 41 }
  1 +package astexpr
  2 +
  3 +import (
  4 + "fmt"
  5 + "github.com/go-gota/gota/dataframe"
  6 + "github.com/go-gota/gota/series"
  7 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  8 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/utils"
  9 + "strings"
  10 +)
  11 +
  12 +type Calculator struct {
  13 + ExprAST ExprAST
  14 + DataTable *domain.DataTable
  15 + Result []string
  16 +}
  17 +
  18 +func NewCalculator(expr string) (*Calculator, error) {
  19 + toks, err := ParseToken(expr)
  20 + if err != nil {
  21 + return nil, err
  22 + }
  23 + ast := NewAST(toks, expr)
  24 + if ast.Err != nil {
  25 + return nil, ast.Err
  26 + }
  27 + ar := ast.ParseExpression()
  28 + if ast.Err != nil {
  29 + return nil, ast.Err
  30 + }
  31 +
  32 + cal := &Calculator{
  33 + ExprAST: ar,
  34 + }
  35 + return cal, nil
  36 +}
  37 +
  38 +func (cal *Calculator) SetDataTable(t *domain.DataTable) *Calculator {
  39 + cal.DataTable = t
  40 + return cal
  41 +}
  42 +
  43 +func (cal *Calculator) Exec() error {
  44 + return nil
  45 +}
  46 +
  47 +func (cal *Calculator) ExprASTResult(ast ExprAST) (*param, error) {
  48 + switch ast.(type) {
  49 + case BinaryExprAST:
  50 + var l, r *param
  51 + var err error
  52 + ast := ast.(BinaryExprAST)
  53 + l, err = cal.ExprASTResult(ast.Lhs)
  54 + if err != nil {
  55 + return nil, err
  56 + }
  57 + r, err = cal.ExprASTResult(ast.Rhs)
  58 + if err != nil {
  59 + return nil, err
  60 + }
  61 + switch ast.Op {
  62 + case "+", "-", "*", "/", "%":
  63 + return cal.OpCalc(ast.Op, l, r), nil
  64 + default:
  65 +
  66 + }
  67 + case NumberExprAST:
  68 + f := ast.(NumberExprAST)
  69 + return NewResult([]string{f.Str}), nil
  70 + case ValueExprAST:
  71 + f := ast.(ValueExprAST)
  72 + return NewResult([]string{f.Val}), nil
  73 + case FieldExprAST:
  74 + f := ast.(FieldExprAST)
  75 + values := cal.DataTable.Values(&domain.Field{SQLName: f.Field.FieldSqlName})
  76 + return NewResult(values), nil
  77 + case FunCallerExprAST:
  78 + f := ast.(FunCallerExprAST)
  79 + //def := defFunc[f.Name]
  80 + //def.fun(f.Args...)
  81 +
  82 + args := make([]*param, 0)
  83 + for i := range f.Args {
  84 + argValue, err := cal.ExprASTResult(f.Args[i])
  85 + if err != nil {
  86 + return nil, err
  87 + }
  88 + args = append(args, argValue)
  89 + }
  90 + return cal.callDef(f.Name, args), nil
  91 + }
  92 + return nil, nil
  93 +}
  94 +
  95 +func (cal *Calculator) callDef(name string, args []*param) *param {
  96 + switch strings.ToLower(name) {
  97 + case "sum":
  98 + return cal.sum(args...)
  99 + case "sumifs":
  100 + return cal.sumifs(args...)
  101 + case "countifs":
  102 + return cal.countifs(args...)
  103 + }
  104 + return cal.sum(args...)
  105 +}
  106 +
  107 +func (cal *Calculator) sum(params ...*param) *param {
  108 + var res = make([]string, 0)
  109 + var total float64
  110 + for _, p := range params {
  111 + for _, v := range p.data {
  112 + total += utils.NewNumberString(v).MustFloat64()
  113 + }
  114 + }
  115 + res = append(res, utils.AssertString(total))
  116 + return NewResult(res)
  117 +}
  118 +
  119 +func (cal *Calculator) sumifs(params ...*param) *param {
  120 + var list = make([]series.Series, 0)
  121 + var filters = make([]dataframe.F, 0)
  122 + for i := 0; i < len(params)-1; i++ {
  123 + col := colName(i)
  124 + if i == 0 {
  125 + list = append(list, series.New(params[i].Data(), series.Float, col))
  126 + continue
  127 + }
  128 + if i%2 == 1 {
  129 + list = append(list, series.New(params[i+1].Data(), series.String, col))
  130 + if f, ok := cal.resolverFilter(col, params[i]); ok {
  131 + filters = append(filters, f)
  132 + }
  133 + i++
  134 + }
  135 + }
  136 + df := dataframe.New(list...)
  137 + df = df.FilterAggregation(dataframe.And, filters...)
  138 + s := df.Col("A0")
  139 + return NewResult(s.Records())
  140 +}
  141 +
  142 +func (cal *Calculator) countifs(params ...*param) *param {
  143 + var list = make([]series.Series, 0)
  144 + var filters = make([]dataframe.F, 0)
  145 + for i := 0; i < len(params)-1; i++ {
  146 + col := colName(i)
  147 + if i%2 == 0 {
  148 + list = append(list, series.New(params[i].Data(), series.String, col))
  149 + if f, ok := cal.resolverFilter(col, params[i+1]); ok {
  150 + filters = append(filters, f)
  151 + }
  152 + i++
  153 + }
  154 + }
  155 + df := dataframe.New(list...)
  156 + df = df.FilterAggregation(dataframe.And, filters...)
  157 + count := df.Col("A0").Len()
  158 + return NewResult([]string{fmt.Sprintf("%d", count)})
  159 +}
  160 +
  161 +func (cal *Calculator) resolverFilter(key string, param *param) (dataframe.F, bool) {
  162 + if len(param.Data()) == 1 {
  163 + condition := param.Data()[0]
  164 + tokens, _ := ParseToken(formatTok(condition))
  165 +
  166 + switch tokens[0].Type {
  167 + case Operator, CompareOperator:
  168 + if tokens[0].Tok == "*" {
  169 + return dataframe.F{Colname: key, Comparator: series.CompFunc, Comparando: func(el series.Element) bool {
  170 + return strings.Contains(el.String(), strings.Trim(formatTok(condition), "*"))
  171 + }}, true
  172 + }
  173 + return dataframe.F{Colname: key, Comparator: series.Comparator(tokens[0].Tok), Comparando: formatTok(tokens[1].Tok)}, true
  174 + case Identifier, Literal, StringArgs:
  175 + if tokens[len(tokens)-1].Tok == "*" || tokens[0].Tok == "*" {
  176 + return dataframe.F{Colname: key, Comparator: series.CompFunc, Comparando: func(el series.Element) bool {
  177 + return strings.Contains(el.String(), strings.Trim(formatTok(condition), "*"))
  178 + }}, true
  179 + }
  180 + return dataframe.F{Colname: key, Comparator: series.Eq, Comparando: formatTok(condition)}, true
  181 + }
  182 + }
  183 + return dataframe.F{}, false
  184 +}
  185 +
  186 +func colName(i int) string {
  187 + return fmt.Sprintf("A%v", i)
  188 +}
  189 +
  190 +func formatTok(tok string) string {
  191 + return strings.Trim(tok, `"`)
  192 +}
  193 +
  194 +func (cal *Calculator) OpCalc(op string, lp *param, rp *param) *param {
  195 + var res = make([]string, 0)
  196 + temp := make([]string, 0)
  197 + temp = lp.Data()
  198 + l := lp.Data()
  199 + r := rp.Data()
  200 + if lp.Len() < rp.Len() {
  201 + l = r
  202 + r = temp
  203 + }
  204 + rIsSingleValue := len(r) == 1
  205 + var rValue string
  206 + if rIsSingleValue {
  207 + rValue = r[0]
  208 + }
  209 + for i, lValue := range l {
  210 + if rIsSingleValue {
  211 + res = append(res, opCalc(op, lValue, rValue))
  212 + continue
  213 + }
  214 + if i >= len(r) {
  215 + break
  216 + }
  217 + res = append(res, opCalc(op, lValue, r[i]))
  218 + }
  219 + return NewResult(res)
  220 +}
  221 +
  222 +func opCalc(op, v1, v2 string) string {
  223 + fv1 := utils.NumberString(v1).MustFloat64()
  224 + fv2 := utils.NumberString(v2).MustFloat64()
  225 + switch op {
  226 + case "+":
  227 + return utils.AssertString(fv1 + fv2)
  228 + case "-":
  229 + return utils.AssertString(fv1 - fv2)
  230 + case "*":
  231 + return utils.AssertString(fv1 * fv2)
  232 + case "/":
  233 + return utils.AssertString(fv1 / fv2)
  234 + }
  235 + return ""
  236 +}
  237 +
  238 +type param struct {
  239 + data []string
  240 +}
  241 +
  242 +func (p *param) Len() int {
  243 + return len(p.data)
  244 +}
  245 +
  246 +func (p *param) Data() []string {
  247 + return p.data
  248 +}
  249 +
  250 +func NewResult(data []string) *param {
  251 + return &param{
  252 + data: data,
  253 + }
  254 +}
  1 +package astexpr
  2 +
  3 +import (
  4 + "github.com/stretchr/testify/assert"
  5 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  6 + "testing"
  7 +)
  8 +
  9 +var table = &domain.DataTable{
  10 + Fields: []*domain.Field{
  11 + {
  12 + Name: "业绩",
  13 + SQLName: "业绩",
  14 + },
  15 + {
  16 + Name: "绩效",
  17 + SQLName: "绩效",
  18 + },
  19 + {
  20 + Name: "月份",
  21 + SQLName: "月份",
  22 + },
  23 + },
  24 + Data: [][]string{
  25 + {
  26 + "10000", "0.90", "2月",
  27 + },
  28 + {
  29 + "20000", "0.95", "3月",
  30 + },
  31 + {
  32 + "20000", "0.95", "3月",
  33 + },
  34 + },
  35 +}
  36 +
  37 +var table1 = &domain.DataTable{
  38 + Fields: []*domain.Field{
  39 + {
  40 + Name: "产品",
  41 + SQLName: "产品",
  42 + },
  43 + {
  44 + Name: "季度",
  45 + SQLName: "季度",
  46 + },
  47 + {
  48 + Name: "数量",
  49 + SQLName: "数量",
  50 + },
  51 + },
  52 + Data: [][]string{
  53 + {
  54 + "苹果", "第一季度", "800",
  55 + },
  56 + {
  57 + "香蕉", "第一季度", "906",
  58 + },
  59 + {
  60 + "西瓜", "第一季度", "968",
  61 + },
  62 + {
  63 + "菠萝", "第一季度", "227",
  64 + },
  65 + {
  66 + "芒果", "第一季度", "612",
  67 + },
  68 + {
  69 + "苹果", "第二季度", "530",
  70 + },
  71 + {
  72 + "香蕉", "第二季度", "950",
  73 + },
  74 + {
  75 + "西瓜", "第二季度", "533",
  76 + },
  77 + {
  78 + "菠萝", "第二季度", "642",
  79 + },
  80 + {
  81 + "芒果", "第二季度", "489",
  82 + },
  83 + },
  84 +}
  85 +
  86 +func TestSumCalculator(t *testing.T) {
  87 + inputs := []struct {
  88 + expr string
  89 + want []string
  90 + }{
  91 + {
  92 + expr: `sum(1000+销售明细.业绩*销售明细.绩效)`,
  93 + want: []string{"50000"},
  94 + },
  95 + {
  96 + expr: `sum(销售明细.业绩)`,
  97 + want: []string{"50000"},
  98 + },
  99 + {
  100 + expr: `sum(销售明细.业绩/10)`,
  101 + want: []string{"5000"},
  102 + },
  103 + {
  104 + expr: `sum(10000,20000,20000)`,
  105 + want: []string{"50000"},
  106 + },
  107 + }
  108 + for _, expr := range inputs {
  109 + calc, err := NewCalculator(expr.expr)
  110 + if err != nil {
  111 + t.Fatal(err)
  112 + }
  113 + calc.SetDataTable(table)
  114 + got, err := calc.ExprASTResult(calc.ExprAST)
  115 + if err != nil {
  116 + t.Fatal(err)
  117 + }
  118 + assert.Equal(t, expr.want, got.data)
  119 + }
  120 +}
  121 +
  122 +func TestSumIfCalculator(t *testing.T) {
  123 + inputs := []struct {
  124 + expr string
  125 + want []string
  126 + }{
  127 + {
  128 + expr: `sum(sumifs(销售明细.业绩,"3月",销售明细.月份))`,
  129 + want: []string{"40000"},
  130 + },
  131 + {
  132 + expr: `sum(sumifs(销售明细.业绩,"3月",销售明细.月份,"<25000",销售明细.业绩))`,
  133 + want: []string{"40000"},
  134 + },
  135 + {
  136 + expr: `sum(sumifs(销售明细.业绩,"3*",销售明细.月份))`,
  137 + want: []string{"40000"},
  138 + },
  139 + {
  140 + expr: `sum(sumifs(销售明细.业绩,"*月",销售明细.月份))`,
  141 + want: []string{"50000"},
  142 + },
  143 + }
  144 + for _, expr := range inputs {
  145 + calc, err := NewCalculator(expr.expr)
  146 + if err != nil {
  147 + t.Fatal(err)
  148 + }
  149 + calc.SetDataTable(table)
  150 + got, err := calc.ExprASTResult(calc.ExprAST)
  151 + if err != nil {
  152 + t.Fatal(err)
  153 + }
  154 + assert.Equal(t, expr.want, got.data)
  155 + }
  156 +}
  157 +
  158 +func TestCountIfCalculator(t *testing.T) {
  159 + inputs := []struct {
  160 + expr string
  161 + want []string
  162 + }{
  163 + {
  164 + expr: `countifs(水果季度.产品,"苹果")`,
  165 + want: []string{"2"},
  166 + },
  167 + {
  168 + expr: `countifs(水果季度.产品,"*果")`,
  169 + want: []string{"4"},
  170 + },
  171 + {
  172 + expr: `countifs(水果季度.季度,"第一季度",水果季度.数量,">600")`,
  173 + want: []string{"4"},
  174 + },
  175 + {
  176 + expr: `countifs(水果季度.产品,"*果",水果季度.数量,">600")`,
  177 + want: []string{"2"},
  178 + },
  179 + }
  180 + for _, expr := range inputs {
  181 + calc, err := NewCalculator(expr.expr)
  182 + if err != nil {
  183 + t.Fatal(err)
  184 + }
  185 + calc.SetDataTable(table1)
  186 + got, err := calc.ExprASTResult(calc.ExprAST)
  187 + if err != nil {
  188 + t.Fatal(err)
  189 + }
  190 + assert.Equal(t, expr.want, got.data)
  191 + }
  192 +}
1 package astexpr 1 package astexpr
2 2
3 import ( 3 import (
  4 + "fmt"
  5 + "testing"
  6 +
4 "github.com/linmadan/egglib-go/utils/json" 7 "github.com/linmadan/egglib-go/utils/json"
5 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/assert"
6 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" 9 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
7 - "testing"  
8 ) 10 )
9 11
10 func TestAstExprUnmarshalJSON(t *testing.T) { 12 func TestAstExprUnmarshalJSON(t *testing.T) {
@@ -99,14 +101,33 @@ func TestAstExprParse(t *testing.T) { @@ -99,14 +101,33 @@ func TestAstExprParse(t *testing.T) {
99 funs := []struct { 101 funs := []struct {
100 Name string 102 Name string
101 Exp []string 103 Exp []string
  104 + Debug bool
102 }{ 105 }{
103 { 106 {
104 - "多级嵌套",  
105 - []string{`COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100")`}, 107 + "COUNTIF 多级嵌套",
  108 + []string{
  109 + `COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100")`,
  110 + `SUM(1/(COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100")))`,
  111 + },
  112 + true,
  113 + },
  114 + {
  115 + "COUNTIF 多级嵌套",
  116 + []string{
  117 + `COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100")`,
  118 + `SUM(COUNTIF(销售明细.业绩,"<=1000")-COUNTIF(销售明细.业绩,"<=100"))`,
  119 + },
  120 + false,
106 }, 121 },
107 } 122 }
108 for _, f := range funs { 123 for _, f := range funs {
  124 + if !f.Debug {
  125 + continue
  126 + }
  127 + fmt.Println("测试项目", f.Name)
  128 + fmt.Println()
109 for _, exp := range f.Exp { 129 for _, exp := range f.Exp {
  130 + fmt.Println("表达式:", exp)
110 r, err := Parse(exp) 131 r, err := Parse(exp)
111 if err != nil { 132 if err != nil {
112 t.Error(err) 133 t.Error(err)
@@ -38,8 +38,8 @@ type Token struct { @@ -38,8 +38,8 @@ type Token struct {
38 38
39 type Parser struct { 39 type Parser struct {
40 Source string 40 Source string
41 -  
42 - ch byte 41 + SourceRunes []rune
  42 + ch rune
43 offset int 43 offset int
44 44
45 err error 45 err error
@@ -48,9 +48,11 @@ type Parser struct { @@ -48,9 +48,11 @@ type Parser struct {
48 func ParseToken(s string) ([]*Token, error) { 48 func ParseToken(s string) ([]*Token, error) {
49 p := &Parser{ 49 p := &Parser{
50 Source: s, 50 Source: s,
  51 + SourceRunes: []rune(s),
51 err: nil, 52 err: nil,
52 - ch: s[0], 53 + //ch: s[0],
53 } 54 }
  55 + p.ch = p.SourceRunes[0]
54 toks := p.parse() 56 toks := p.parse()
55 if p.err != nil { 57 if p.err != nil {
56 return nil, p.err 58 return nil, p.err
@@ -71,7 +73,7 @@ func (p *Parser) parse() []*Token { @@ -71,7 +73,7 @@ func (p *Parser) parse() []*Token {
71 } 73 }
72 74
73 func (p *Parser) nextTok() *Token { 75 func (p *Parser) nextTok() *Token {
74 - if p.offset >= len(p.Source) || p.err != nil { 76 + if p.offset >= len(p.SourceRunes) || p.err != nil {
75 return nil 77 return nil
76 } 78 }
77 var err error 79 var err error
@@ -105,7 +107,7 @@ func (p *Parser) nextTok() *Token { @@ -105,7 +107,7 @@ func (p *Parser) nextTok() *Token {
105 for p.isCompareWordChar(p.ch) && p.nextCh() == nil { 107 for p.isCompareWordChar(p.ch) && p.nextCh() == nil {
106 } 108 }
107 tok = &Token{ 109 tok = &Token{
108 - Tok: p.Source[start:p.offset], 110 + Tok: string(p.SourceRunes[start:p.offset]),
109 Type: CompareOperator, 111 Type: CompareOperator,
110 } 112 }
111 tok.Offset = start 113 tok.Offset = start
@@ -128,24 +130,24 @@ func (p *Parser) nextTok() *Token { @@ -128,24 +130,24 @@ func (p *Parser) nextTok() *Token {
128 '8', 130 '8',
129 '9': 131 '9':
130 for p.isDigitNum(p.ch) && p.nextCh() == nil { 132 for p.isDigitNum(p.ch) && p.nextCh() == nil {
131 - if (p.ch == '-' || p.ch == '+') && p.Source[p.offset-1] != 'e' { 133 + if (p.ch == '-' || p.ch == '+') && p.SourceRunes[p.offset-1] != 'e' {
132 break 134 break
133 } 135 }
134 } 136 }
135 tok = &Token{ 137 tok = &Token{
136 - Tok: strings.ReplaceAll(p.Source[start:p.offset], "_", ""), 138 + Tok: strings.ReplaceAll(string(p.SourceRunes[start:p.offset]), "_", ""),
137 Type: Literal, 139 Type: Literal,
138 } 140 }
139 tok.Offset = start 141 tok.Offset = start
140 case '"': 142 case '"':
141 - for (p.isDigitNum(p.ch) || p.isChar(p.ch) || p.isCompareWordChar(p.ch)) && p.nextCh() == nil { 143 + for (p.isDigitNum(p.ch) || p.isChar(p.ch) || p.isCompareWordChar(p.ch) || p.ch == '*') && p.nextCh() == nil {
142 if p.ch == '"' { 144 if p.ch == '"' {
143 break 145 break
144 } 146 }
145 } 147 }
146 err = p.nextCh() 148 err = p.nextCh()
147 tok = &Token{ 149 tok = &Token{
148 - Tok: p.Source[start:p.offset], 150 + Tok: string(p.SourceRunes[start:p.offset]),
149 Type: StringArgs, 151 Type: StringArgs,
150 } 152 }
151 tok.Offset = start 153 tok.Offset = start
@@ -162,7 +164,7 @@ func (p *Parser) nextTok() *Token { @@ -162,7 +164,7 @@ func (p *Parser) nextTok() *Token {
162 for p.isWordChar(p.ch) && p.nextCh() == nil { 164 for p.isWordChar(p.ch) && p.nextCh() == nil {
163 } 165 }
164 tok = &Token{ 166 tok = &Token{
165 - Tok: p.Source[start:p.offset], 167 + Tok: string(p.SourceRunes[start:p.offset]),
166 Type: Identifier, 168 Type: Identifier,
167 } 169 }
168 tok.Offset = start 170 tok.Offset = start
@@ -179,14 +181,14 @@ func (p *Parser) nextTok() *Token { @@ -179,14 +181,14 @@ func (p *Parser) nextTok() *Token {
179 181
180 func (p *Parser) nextCh() error { 182 func (p *Parser) nextCh() error {
181 p.offset++ 183 p.offset++
182 - if p.offset < len(p.Source) {  
183 - p.ch = p.Source[p.offset] 184 + if p.offset < len(p.SourceRunes) {
  185 + p.ch = p.SourceRunes[p.offset]
184 return nil 186 return nil
185 } 187 }
186 return errors.New("EOF") 188 return errors.New("EOF")
187 } 189 }
188 190
189 -func (p *Parser) isWhitespace(c byte) bool { 191 +func (p *Parser) isWhitespace(c rune) bool {
190 return c == ' ' || 192 return c == ' ' ||
191 c == '\t' || 193 c == '\t' ||
192 c == '\n' || 194 c == '\n' ||
@@ -195,18 +197,23 @@ func (p *Parser) isWhitespace(c byte) bool { @@ -195,18 +197,23 @@ func (p *Parser) isWhitespace(c byte) bool {
195 c == '\r' 197 c == '\r'
196 } 198 }
197 199
198 -func (p *Parser) isDigitNum(c byte) bool { 200 +func (p *Parser) isDigitNum(c rune) bool {
199 return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+' 201 return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'
200 } 202 }
201 203
202 -func (p *Parser) isChar(c byte) bool {  
203 - return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '.' || c == '"' 204 +func (p *Parser) isChar(c rune) bool {
  205 + return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '.' || c == '"' || isChineseCharacter(c)
  206 + //判断是汉字
204 } 207 }
205 208
206 -func (p *Parser) isWordChar(c byte) bool { 209 +func (p *Parser) isWordChar(c rune) bool {
207 return p.isChar(c) || '0' <= c && c <= '9' 210 return p.isChar(c) || '0' <= c && c <= '9'
208 } 211 }
209 212
210 -func (p *Parser) isCompareWordChar(c byte) bool { 213 +func (p *Parser) isCompareWordChar(c rune) bool {
211 return c == '=' || c == '<' || c == '>' 214 return c == '=' || c == '<' || c == '>'
212 } 215 }
  216 +
  217 +func isChineseCharacter(c rune) bool {
  218 + return len([]byte(string(c))) > 2
  219 +}
1 package astexpr 1 package astexpr
2 2
3 import ( 3 import (
  4 + "bytes"
4 "encoding/json" 5 "encoding/json"
5 "fmt" 6 "fmt"
6 "testing" 7 "testing"
@@ -267,12 +268,14 @@ func Parse(s string) (r float64, err error) { @@ -267,12 +268,14 @@ func Parse(s string) (r float64, err error) {
267 err = e.(error) 268 err = e.(error)
268 } 269 }
269 }() 270 }()
270 - if ar != nil {  
271 - fmt.Printf("ExprAST: %+v\n", ar)  
272 - }  
273 - arData, _ := json.Marshal(ar)  
274 - fmt.Printf("%s\n", string(arData))  
275 - 271 + buf := bytes.NewBuffer(nil)
  272 + enc := json.NewEncoder(buf)
  273 + enc.SetEscapeHTML(false)
  274 + enc.Encode(ar)
  275 + // if ar != nil {
  276 + // fmt.Printf("ExprAST: %+v\n", ar)
  277 + // }
  278 + fmt.Println(buf.String())
276 return 0, err 279 return 0, err
277 } 280 }
278 281
@@ -160,7 +160,7 @@ func (t TableType) ToString() string { @@ -160,7 +160,7 @@ func (t TableType) ToString() string {
160 } 160 }
161 161
162 func (t TableType) TableStatusEditable() bool { 162 func (t TableType) TableStatusEditable() bool {
163 - return t == SchemaTable || t == CalculateItem || t == CalculateTable || t == CalculateSet 163 + return t == SchemaTable || t == CalculateItem || t == CalculateSet
164 } 164 }
165 165
166 func (t TableType) TableHasGroup() bool { 166 func (t TableType) TableHasGroup() bool {
@@ -133,6 +133,25 @@ func (querySet *QuerySet) GetDependencyTables(queryComponents []*QueryComponent) @@ -133,6 +133,25 @@ func (querySet *QuerySet) GetDependencyTables(queryComponents []*QueryComponent)
133 return res 133 return res
134 } 134 }
135 135
  136 +func (querySet *QuerySet) Valid(queryComponents []*QueryComponent) error {
  137 + switch querySet.Type {
  138 + case CalculateTable.ToString():
  139 + if len(queryComponents) == 0 {
  140 + return fmt.Errorf("行、值不能同时为空")
  141 + }
  142 + qc := queryComponents[0]
  143 + set := collection.NewSet()
  144 + for _, f := range qc.Aggregation.AggregationFields() {
  145 + if !set.Contains(f.DisplayName) {
  146 + set.AddStr(f.DisplayName)
  147 + } else {
  148 + return fmt.Errorf("字段'%s'存在重名,请进行重命名", f.DisplayName)
  149 + }
  150 + }
  151 + }
  152 + return nil
  153 +}
  154 +
136 type QuerySets []*QuerySet 155 type QuerySets []*QuerySet
137 156
138 func (querySets QuerySets) ToMap() map[int]*QuerySet { 157 func (querySets QuerySets) ToMap() map[int]*QuerySet {
@@ -188,7 +188,7 @@ func NewTableAppendRequest(param domain.ReqAppendData) TableAppendRequest { @@ -188,7 +188,7 @@ func NewTableAppendRequest(param domain.ReqAppendData) TableAppendRequest {
188 OriginalTableId: intToString(param.FileId), 188 OriginalTableId: intToString(param.FileId),
189 CheckoutTableFileUrl: param.FileUrl, 189 CheckoutTableFileUrl: param.FileUrl,
190 DatabaseTableName: param.Table.SQLName, 190 DatabaseTableName: param.Table.SQLName,
191 - ColumnSchemas: DomainFieldsToColumnSchemas(param.ExcelTable.DataFields), 191 + ColumnSchemas: DomainFieldsToColumnSchemas(param.From), //param.ExcelTable.DataFields
192 FieldSchemas: ToFieldSchemas(param.Table.DataFields), 192 FieldSchemas: ToFieldSchemas(param.Table.DataFields),
193 SchemaMap: make(map[string]domain.ColumnSchema), 193 SchemaMap: make(map[string]domain.ColumnSchema),
194 } 194 }
@@ -8,6 +8,7 @@ import ( @@ -8,6 +8,7 @@ import (
8 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/redis" 8 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/redis"
9 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository" 9 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository"
10 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/starrocks" 10 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/starrocks"
  11 + "reflect"
11 "strings" 12 "strings"
12 "time" 13 "time"
13 ) 14 )
@@ -273,6 +274,9 @@ func (ptr *QuerySetService) PreviewPrepare(ctx *domain.Context, querySetId int, @@ -273,6 +274,9 @@ func (ptr *QuerySetService) PreviewPrepare(ctx *domain.Context, querySetId int,
273 if err != nil { 274 if err != nil {
274 return nil, err 275 return nil, err
275 } 276 }
  277 + if err = querySet.Valid(queryComponents); err != nil {
  278 + return nil, err
  279 + }
276 if !queryComponentsHasEdit(ctx, querySet, queryComponents) && querySet.QuerySetInfo.BindTableId > 0 { 280 if !queryComponentsHasEdit(ctx, querySet, queryComponents) && querySet.QuerySetInfo.BindTableId > 0 {
277 if t, _ := tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": querySet.QuerySetInfo.BindTableId}); t != nil { 281 if t, _ := tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": querySet.QuerySetInfo.BindTableId}); t != nil {
278 return t, nil 282 return t, nil
@@ -655,6 +659,29 @@ func aggregationEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryCom @@ -655,6 +659,29 @@ func aggregationEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryCom
655 return res 659 return res
656 } 660 }
657 661
  662 +func aggregationHasEdit(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) bool {
  663 + if len(queryComponents) > 0 && len(querySet.QueryComponents) > 0 {
  664 + oldQC := querySet.QueryComponents[0]
  665 + newQC := queryComponents[0]
  666 + if oldQC.Aggregation == nil || newQC.Aggregation == nil {
  667 + return false
  668 + }
  669 +
  670 + c1 := make([]string, 0)
  671 + for _, f := range oldQC.Aggregation.AggregationFields() {
  672 + c1 = append(c1, f.Expr.ExprSql)
  673 + }
  674 + c2 := make([]string, 0)
  675 + for _, f := range newQC.Aggregation.AggregationFields() {
  676 + c2 = append(c2, f.Expr.ExprSql)
  677 + }
  678 + if !reflect.DeepEqual(c1, c2) {
  679 + return true
  680 + }
  681 + }
  682 + return false
  683 +}
  684 +
658 func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) bool { 685 func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) bool {
659 logs := selectsEditLog(ctx, querySet, queryComponents) 686 logs := selectsEditLog(ctx, querySet, queryComponents)
660 if len(logs) > 0 { 687 if len(logs) > 0 {
@@ -668,6 +695,9 @@ func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, quer @@ -668,6 +695,9 @@ func queryComponentsHasEdit(ctx *domain.Context, querySet *domain.QuerySet, quer
668 if len(logs) > 0 { 695 if len(logs) > 0 {
669 return true 696 return true
670 } 697 }
  698 + if aggregationHasEdit(ctx, querySet, queryComponents) {
  699 + return true
  700 + }
671 for _, item := range queryComponents { 701 for _, item := range queryComponents {
672 if len(item.Id) == 0 { 702 if len(item.Id) == 0 {
673 return true 703 return true
@@ -757,6 +787,9 @@ func (ptr *QuerySetService) CreateOrUpdateCalculateTable(ctx *domain.Context, qu @@ -757,6 +787,9 @@ func (ptr *QuerySetService) CreateOrUpdateCalculateTable(ctx *domain.Context, qu
757 err error 787 err error
758 foundMasterTable *domain.Table 788 foundMasterTable *domain.Table
759 ) 789 )
  790 + if err = querySet.Valid(queryComponents); err != nil {
  791 + return nil, err
  792 + }
760 dependencyTables := querySet.GetDependencyTables(queryComponents) 793 dependencyTables := querySet.GetDependencyTables(queryComponents)
761 tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) 794 tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
762 foundMasterTable, err = tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": masterTable.TableId}) 795 foundMasterTable, err = tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": masterTable.TableId})
@@ -85,6 +85,7 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int, @@ -85,6 +85,7 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int,
85 "result": fmt.Sprintf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name), 85 "result": fmt.Sprintf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name),
86 }, nil 86 }, nil
87 } 87 }
  88 + fromField.SQLType = toField.SQLType // 兼容 INT BIGINT
88 requestData.To = append(requestData.To, toField) 89 requestData.To = append(requestData.To, toField)
89 requestData.From = append(requestData.From, fromField) 90 requestData.From = append(requestData.From, fromField)
90 } 91 }