正在显示
13 个修改的文件
包含
572 行增加
和
38 行删除
@@ -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 |
@@ -34,8 +34,9 @@ func NewFieldExprAST(val string) FieldExprAST { | @@ -34,8 +34,9 @@ func NewFieldExprAST(val string) FieldExprAST { | ||
34 | ExprType: TypeFieldExprAST, | 34 | ExprType: TypeFieldExprAST, |
35 | Str: val, | 35 | Str: val, |
36 | Field: &domain.TableField{ | 36 | Field: &domain.TableField{ |
37 | - FieldName: filed, | ||
38 | - TableName: table, | 37 | + FieldName: filed, |
38 | + FieldSqlName: filed, | ||
39 | + TableName: table, | ||
39 | }, | 40 | }, |
40 | } | 41 | } |
41 | } | 42 | } |
pkg/domain/astexpr/ast_expr_calculator.go
0 → 100644
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 ¶m{ | ||
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) { |
@@ -97,16 +99,35 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -97,16 +99,35 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
97 | 99 | ||
98 | func TestAstExprParse(t *testing.T) { | 100 | func TestAstExprParse(t *testing.T) { |
99 | funs := []struct { | 101 | funs := []struct { |
100 | - Name string | ||
101 | - Exp []string | 102 | + Name 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) |
@@ -37,20 +37,22 @@ type Token struct { | @@ -37,20 +37,22 @@ type Token struct { | ||
37 | } | 37 | } |
38 | 38 | ||
39 | type Parser struct { | 39 | type Parser struct { |
40 | - Source string | ||
41 | - | ||
42 | - ch byte | ||
43 | - offset int | 40 | + Source string |
41 | + SourceRunes []rune | ||
42 | + ch rune | ||
43 | + offset int | ||
44 | 44 | ||
45 | err error | 45 | err error |
46 | } | 46 | } |
47 | 47 | ||
48 | func ParseToken(s string) ([]*Token, error) { | 48 | func ParseToken(s string) ([]*Token, error) { |
49 | p := &Parser{ | 49 | p := &Parser{ |
50 | - Source: s, | ||
51 | - err: nil, | ||
52 | - ch: s[0], | 50 | + Source: s, |
51 | + SourceRunes: []rune(s), | ||
52 | + err: nil, | ||
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 | } |
-
请 注册 或 登录 后发表评论