正在显示
20 个修改的文件
包含
1976 行增加
和
22 行删除
@@ -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 |
pkg/domain/astexpr/ast.go
0 → 100644
1 | +package astexpr | ||
2 | + | ||
3 | +import ( | ||
4 | + "errors" | ||
5 | + "fmt" | ||
6 | + "strconv" | ||
7 | + "strings" | ||
8 | +) | ||
9 | + | ||
10 | +type AST struct { | ||
11 | + Tokens []*Token | ||
12 | + | ||
13 | + source string | ||
14 | + currTok *Token | ||
15 | + currIndex int | ||
16 | + depth int | ||
17 | + | ||
18 | + Err error | ||
19 | +} | ||
20 | + | ||
21 | +func NewAST(toks []*Token, s string) *AST { | ||
22 | + a := &AST{ | ||
23 | + Tokens: toks, | ||
24 | + source: s, | ||
25 | + } | ||
26 | + if a.Tokens == nil || len(a.Tokens) == 0 { | ||
27 | + a.Err = errors.New("empty token") | ||
28 | + } else { | ||
29 | + a.currIndex = 0 | ||
30 | + a.currTok = a.Tokens[0] | ||
31 | + } | ||
32 | + return a | ||
33 | +} | ||
34 | + | ||
35 | +// ParseExpression 解析表达式 | ||
36 | +func (a *AST) ParseExpression() ExprAST { | ||
37 | + a.depth++ // called depth | ||
38 | + lhs := a.parsePrimary() | ||
39 | + r := a.parseBinOpRHS(0, lhs) | ||
40 | + a.depth-- | ||
41 | + if a.depth == 0 && a.currIndex != len(a.Tokens) && a.Err == nil { | ||
42 | + a.Err = errors.New( | ||
43 | + fmt.Sprintf("bad expression, reaching the end or missing the operator\n%s", | ||
44 | + ErrPos(a.source, a.currTok.Offset))) | ||
45 | + } | ||
46 | + return r | ||
47 | +} | ||
48 | + | ||
49 | +func (a *AST) getNextToken() *Token { | ||
50 | + a.currIndex++ | ||
51 | + if a.currIndex < len(a.Tokens) { | ||
52 | + a.currTok = a.Tokens[a.currIndex] | ||
53 | + return a.currTok | ||
54 | + } | ||
55 | + return nil | ||
56 | +} | ||
57 | + | ||
58 | +func (a *AST) getTokPrecedence() int { | ||
59 | + if p, ok := precedence[a.currTok.Tok]; ok { | ||
60 | + return p | ||
61 | + } | ||
62 | + return -1 | ||
63 | +} | ||
64 | + | ||
65 | +func (a *AST) parseNumber() NumberExprAST { | ||
66 | + f64, err := strconv.ParseFloat(a.currTok.Tok, 64) | ||
67 | + if err != nil { | ||
68 | + a.Err = errors.New( | ||
69 | + fmt.Sprintf("%v\nwant '(' or '0-9' but get '%s'\n%s", | ||
70 | + err.Error(), | ||
71 | + a.currTok.Tok, | ||
72 | + ErrPos(a.source, a.currTok.Offset))) | ||
73 | + return NumberExprAST{} | ||
74 | + } | ||
75 | + n := NumberExprAST{ | ||
76 | + Val: f64, | ||
77 | + Str: a.currTok.Tok, | ||
78 | + } | ||
79 | + a.getNextToken() | ||
80 | + return n | ||
81 | +} | ||
82 | + | ||
83 | +func (a *AST) parseFunCallerOrConst() ExprAST { | ||
84 | + name := a.currTok.Tok | ||
85 | + a.getNextToken() | ||
86 | + // call func | ||
87 | + if a.currTok.Tok == "(" { | ||
88 | + f := FunCallerExprAST{} | ||
89 | + if _, ok := defFunc[strings.ToLower(name)]; !ok { | ||
90 | + a.Err = errors.New( | ||
91 | + fmt.Sprintf("function `%s` is undefined\n%s", | ||
92 | + name, | ||
93 | + ErrPos(a.source, a.currTok.Offset))) | ||
94 | + return f | ||
95 | + } | ||
96 | + a.getNextToken() | ||
97 | + exprs := make([]ExprAST, 0) | ||
98 | + if a.currTok.Tok == ")" { | ||
99 | + // function call without parameters | ||
100 | + // ignore the process of parameter resolution | ||
101 | + } else { | ||
102 | + exprs = append(exprs, a.ParseExpression()) | ||
103 | + for a.currTok.Tok != ")" && a.getNextToken() != nil { | ||
104 | + if a.currTok.Type == COMMA { | ||
105 | + continue | ||
106 | + } | ||
107 | + exprs = append(exprs, a.ParseExpression()) | ||
108 | + } | ||
109 | + } | ||
110 | + def := defFunc[strings.ToLower(name)] | ||
111 | + if def.argc >= 0 && len(exprs) != def.argc { | ||
112 | + a.Err = errors.New( | ||
113 | + fmt.Sprintf("wrong way calling function `%s`, parameters want %d but get %d\n%s", | ||
114 | + name, | ||
115 | + def.argc, | ||
116 | + len(exprs), | ||
117 | + ErrPos(a.source, a.currTok.Offset))) | ||
118 | + } | ||
119 | + a.getNextToken() | ||
120 | + f = NewFunCallerExprAST(name, exprs...) | ||
121 | + return f | ||
122 | + } | ||
123 | + // call const | ||
124 | + if v, ok := defConst[name]; ok { | ||
125 | + return NumberExprAST{ | ||
126 | + Val: v, | ||
127 | + Str: strconv.FormatFloat(v, 'f', 0, 64), | ||
128 | + } | ||
129 | + } else { | ||
130 | + if strings.Contains(name, ".") { | ||
131 | + return NewFieldExprAST(name) | ||
132 | + } | ||
133 | + a.Err = errors.New( | ||
134 | + fmt.Sprintf("const `%s` is undefined\n%s", | ||
135 | + name, | ||
136 | + ErrPos(a.source, a.currTok.Offset))) | ||
137 | + return NumberExprAST{} | ||
138 | + } | ||
139 | +} | ||
140 | + | ||
141 | +func (a *AST) parsePrimary() ExprAST { | ||
142 | + switch a.currTok.Type { | ||
143 | + case Identifier: | ||
144 | + return a.parseFunCallerOrConst() | ||
145 | + case Literal: | ||
146 | + return a.parseNumber() | ||
147 | + case StringArgs: | ||
148 | + e := NewValueExprAST(a.currTok.Tok) | ||
149 | + a.getNextToken() | ||
150 | + return e | ||
151 | + case Operator: | ||
152 | + if a.currTok.Tok == "(" { | ||
153 | + t := a.getNextToken() | ||
154 | + if t == nil { | ||
155 | + a.Err = errors.New( | ||
156 | + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", | ||
157 | + ErrPos(a.source, a.currTok.Offset))) | ||
158 | + return nil | ||
159 | + } | ||
160 | + e := a.ParseExpression() | ||
161 | + if e == nil { | ||
162 | + return nil | ||
163 | + } | ||
164 | + if a.currTok.Tok != ")" { | ||
165 | + a.Err = errors.New( | ||
166 | + fmt.Sprintf("want ')' but get %s\n%s", | ||
167 | + a.currTok.Tok, | ||
168 | + ErrPos(a.source, a.currTok.Offset))) | ||
169 | + return nil | ||
170 | + } | ||
171 | + a.getNextToken() | ||
172 | + return e | ||
173 | + } else if a.currTok.Tok == "-" { | ||
174 | + if a.getNextToken() == nil { | ||
175 | + a.Err = errors.New( | ||
176 | + fmt.Sprintf("want '0-9' but get '-'\n%s", | ||
177 | + ErrPos(a.source, a.currTok.Offset))) | ||
178 | + return nil | ||
179 | + } | ||
180 | + bin := NewBinaryExprAST("-", NumberExprAST{}, a.parsePrimary()) | ||
181 | + return bin | ||
182 | + } else { | ||
183 | + return a.parseNumber() | ||
184 | + } | ||
185 | + case COMMA: | ||
186 | + a.Err = errors.New( | ||
187 | + fmt.Sprintf("want '(' or '0-9' but get %s\n%s", | ||
188 | + a.currTok.Tok, | ||
189 | + ErrPos(a.source, a.currTok.Offset))) | ||
190 | + return nil | ||
191 | + default: | ||
192 | + return nil | ||
193 | + } | ||
194 | +} | ||
195 | + | ||
196 | +func (a *AST) parseBinOpRHS(execPrec int, lhs ExprAST) ExprAST { | ||
197 | + for { | ||
198 | + tokPrec := a.getTokPrecedence() | ||
199 | + if tokPrec < execPrec { | ||
200 | + return lhs | ||
201 | + } | ||
202 | + binOp := a.currTok.Tok | ||
203 | + if a.getNextToken() == nil { | ||
204 | + a.Err = errors.New( | ||
205 | + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", | ||
206 | + ErrPos(a.source, a.currTok.Offset))) | ||
207 | + return nil | ||
208 | + } | ||
209 | + rhs := a.parsePrimary() | ||
210 | + if rhs == nil { | ||
211 | + return nil | ||
212 | + } | ||
213 | + nextPrec := a.getTokPrecedence() | ||
214 | + if tokPrec < nextPrec { | ||
215 | + rhs = a.parseBinOpRHS(tokPrec+1, rhs) | ||
216 | + if rhs == nil { | ||
217 | + return nil | ||
218 | + } | ||
219 | + } | ||
220 | + lhs = NewBinaryExprAST(binOp, lhs, rhs) | ||
221 | + } | ||
222 | +} |
pkg/domain/astexpr/ast_def.go
0 → 100644
1 | +package astexpr | ||
2 | + | ||
3 | +import ( | ||
4 | + "errors" | ||
5 | + "math" | ||
6 | +) | ||
7 | + | ||
8 | +const ( | ||
9 | + RadianMode = iota | ||
10 | + AngleMode | ||
11 | +) | ||
12 | + | ||
13 | +type defS struct { | ||
14 | + argc int | ||
15 | + fun func(expr ...ExprAST) float64 | ||
16 | +} | ||
17 | + | ||
18 | +// TrigonometricMode enum "RadianMode", "AngleMode" | ||
19 | +var TrigonometricMode = RadianMode | ||
20 | + | ||
21 | +var defConst = map[string]float64{ | ||
22 | + "pi": math.Pi, | ||
23 | +} | ||
24 | + | ||
25 | +var defFunc map[string]defS | ||
26 | + | ||
27 | +func init() { | ||
28 | + defFunc = map[string]defS{ | ||
29 | + "sin": {1, defSin}, | ||
30 | + "cos": {1, defCos}, | ||
31 | + "tan": {1, defTan}, | ||
32 | + "cot": {1, defCot}, | ||
33 | + "sec": {1, defSec}, | ||
34 | + "csc": {1, defCsc}, | ||
35 | + | ||
36 | + "abs": {1, defAbs}, | ||
37 | + "ceil": {1, defCeil}, | ||
38 | + "floor": {1, defFloor}, | ||
39 | + "round": {1, defRound}, | ||
40 | + "sqrt": {1, defSqrt}, | ||
41 | + "cbrt": {1, defCbrt}, | ||
42 | + | ||
43 | + "noerr": {1, defNoErr}, | ||
44 | + | ||
45 | + "max": {-1, defMax}, | ||
46 | + "min": {-1, defMin}, | ||
47 | + | ||
48 | + // excel support | ||
49 | + "sum": {-1, defNone}, | ||
50 | + "if": {-1, defNone}, | ||
51 | + "sumif": {-1, defNone}, | ||
52 | + "and": {-1, defNone}, | ||
53 | + "or": {-1, defNone}, | ||
54 | + "month": {-1, defNone}, | ||
55 | + "year": {-1, defNone}, | ||
56 | + //"round": {-1, defNone}, | ||
57 | + "rounddown": {-1, defNone}, | ||
58 | + "roundup": {-1, defNone}, | ||
59 | + "count": {-1, defNone}, | ||
60 | + "countifs": {-1, defNone}, | ||
61 | + //"&": {-1, defNone}, | ||
62 | + "concat": {-1, defNone}, | ||
63 | + "sumifs": {-1, defNone}, | ||
64 | + } | ||
65 | +} | ||
66 | + | ||
67 | +// sin(pi/2) = 1 | ||
68 | +func defSin(expr ...ExprAST) float64 { | ||
69 | + return math.Sin(expr2Radian(expr[0])) | ||
70 | +} | ||
71 | + | ||
72 | +// cos(0) = 1 | ||
73 | +func defCos(expr ...ExprAST) float64 { | ||
74 | + return math.Cos(expr2Radian(expr[0])) | ||
75 | +} | ||
76 | + | ||
77 | +// tan(pi/4) = 1 | ||
78 | +func defTan(expr ...ExprAST) float64 { | ||
79 | + return math.Tan(expr2Radian(expr[0])) | ||
80 | +} | ||
81 | + | ||
82 | +// cot(pi/4) = 1 | ||
83 | +func defCot(expr ...ExprAST) float64 { | ||
84 | + return 1 / defTan(expr...) | ||
85 | +} | ||
86 | + | ||
87 | +// sec(0) = 1 | ||
88 | +func defSec(expr ...ExprAST) float64 { | ||
89 | + return 1 / defCos(expr...) | ||
90 | +} | ||
91 | + | ||
92 | +// csc(pi/2) = 1 | ||
93 | +func defCsc(expr ...ExprAST) float64 { | ||
94 | + return 1 / defSin(expr...) | ||
95 | +} | ||
96 | + | ||
97 | +// abs(-2) = 2 | ||
98 | +func defAbs(expr ...ExprAST) float64 { | ||
99 | + return math.Abs(ExprASTResult(expr[0])) | ||
100 | +} | ||
101 | + | ||
102 | +// ceil(4.2) = ceil(4.8) = 5 | ||
103 | +func defCeil(expr ...ExprAST) float64 { | ||
104 | + return math.Ceil(ExprASTResult(expr[0])) | ||
105 | +} | ||
106 | + | ||
107 | +// floor(4.2) = floor(4.8) = 4 | ||
108 | +func defFloor(expr ...ExprAST) float64 { | ||
109 | + return math.Floor(ExprASTResult(expr[0])) | ||
110 | +} | ||
111 | + | ||
112 | +// round(4.2) = 4 | ||
113 | +// round(4.6) = 5 | ||
114 | +func defRound(expr ...ExprAST) float64 { | ||
115 | + return math.Round(ExprASTResult(expr[0])) | ||
116 | +} | ||
117 | + | ||
118 | +// sqrt(4) = 2 | ||
119 | +// sqrt(4) = abs(sqrt(4)) | ||
120 | +// returns only the absolute value of the result | ||
121 | +func defSqrt(expr ...ExprAST) float64 { | ||
122 | + return math.Sqrt(ExprASTResult(expr[0])) | ||
123 | +} | ||
124 | + | ||
125 | +// cbrt(27) = 3 | ||
126 | +func defCbrt(expr ...ExprAST) float64 { | ||
127 | + return math.Cbrt(ExprASTResult(expr[0])) | ||
128 | +} | ||
129 | + | ||
130 | +// max(2) = 2 | ||
131 | +// max(2, 3) = 3 | ||
132 | +// max(2, 3, 1) = 3 | ||
133 | +func defMax(expr ...ExprAST) float64 { | ||
134 | + if len(expr) == 0 { | ||
135 | + panic(errors.New("calling function `max` must have at least one parameter.")) | ||
136 | + } | ||
137 | + if len(expr) == 1 { | ||
138 | + return ExprASTResult(expr[0]) | ||
139 | + } | ||
140 | + maxV := ExprASTResult(expr[0]) | ||
141 | + for i := 1; i < len(expr); i++ { | ||
142 | + v := ExprASTResult(expr[i]) | ||
143 | + maxV = math.Max(maxV, v) | ||
144 | + } | ||
145 | + return maxV | ||
146 | +} | ||
147 | + | ||
148 | +// min(2) = 2 | ||
149 | +// min(2, 3) = 2 | ||
150 | +// min(2, 3, 1) = 1 | ||
151 | +func defMin(expr ...ExprAST) float64 { | ||
152 | + if len(expr) == 0 { | ||
153 | + panic(errors.New("calling function `min` must have at least one parameter.")) | ||
154 | + } | ||
155 | + if len(expr) == 1 { | ||
156 | + return ExprASTResult(expr[0]) | ||
157 | + } | ||
158 | + maxV := ExprASTResult(expr[0]) | ||
159 | + for i := 1; i < len(expr); i++ { | ||
160 | + v := ExprASTResult(expr[i]) | ||
161 | + maxV = math.Min(maxV, v) | ||
162 | + } | ||
163 | + return maxV | ||
164 | +} | ||
165 | + | ||
166 | +// noerr(1/0) = 0 | ||
167 | +// noerr(2.5/(1-1)) = 0 | ||
168 | +func defNoErr(expr ...ExprAST) (r float64) { | ||
169 | + defer func() { | ||
170 | + if e := recover(); e != nil { | ||
171 | + r = 0 | ||
172 | + } | ||
173 | + }() | ||
174 | + return ExprASTResult(expr[0]) | ||
175 | +} | ||
176 | + | ||
177 | +func defNone(expr ...ExprAST) (r float64) { | ||
178 | + return 0 | ||
179 | +} |
1 | -package domain | 1 | +package astexpr |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "fmt" | 5 | "fmt" |
6 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
6 | ) | 7 | ) |
7 | 8 | ||
8 | const ( | 9 | const ( |
@@ -10,12 +11,19 @@ const ( | @@ -10,12 +11,19 @@ const ( | ||
10 | TypeBinaryExprAST = "BinaryExprAST" | 11 | TypeBinaryExprAST = "BinaryExprAST" |
11 | TypeFieldExprAST = "FieldExprAST" | 12 | TypeFieldExprAST = "FieldExprAST" |
12 | TypeValueExprAST = "ValueExprAST" | 13 | TypeValueExprAST = "ValueExprAST" |
14 | + TypeNumberExprAST = "NumberExprAST" | ||
13 | ) | 15 | ) |
14 | 16 | ||
15 | type ExprAST interface { | 17 | type ExprAST interface { |
16 | toStr() string | 18 | toStr() string |
17 | } | 19 | } |
18 | 20 | ||
21 | +type NumberExprAST struct { | ||
22 | + ExprType string `json:"exprType"` | ||
23 | + Val float64 | ||
24 | + Str string | ||
25 | +} | ||
26 | + | ||
19 | type ValueExprAST struct { | 27 | type ValueExprAST struct { |
20 | ExprType string `json:"exprType"` | 28 | ExprType string `json:"exprType"` |
21 | Val string `json:"val"` | 29 | Val string `json:"val"` |
@@ -23,9 +31,9 @@ type ValueExprAST struct { | @@ -23,9 +31,9 @@ type ValueExprAST struct { | ||
23 | } | 31 | } |
24 | 32 | ||
25 | type FieldExprAST struct { | 33 | type FieldExprAST struct { |
26 | - ExprType string `json:"exprType"` | ||
27 | - Str string `json:"str"` | ||
28 | - Field *TableField `json:"field"` | 34 | + ExprType string `json:"exprType"` |
35 | + Str string `json:"str"` | ||
36 | + Field *domain.TableField `json:"field"` | ||
29 | } | 37 | } |
30 | 38 | ||
31 | type BinaryExprAST struct { | 39 | type BinaryExprAST struct { |
@@ -36,10 +44,17 @@ type BinaryExprAST struct { | @@ -36,10 +44,17 @@ type BinaryExprAST struct { | ||
36 | } | 44 | } |
37 | 45 | ||
38 | type FunCallerExprAST struct { | 46 | type FunCallerExprAST struct { |
39 | - ArrayFlag bool `json:"arrayFlag"` | ||
40 | - ExprType string `json:"exprType"` | ||
41 | - Name string `json:"name"` | ||
42 | - Args []ExprAST `json:"args"` | 47 | + //ArrayFlag bool `json:"arrayFlag"` |
48 | + ExprType string `json:"exprType"` | ||
49 | + Name string `json:"name"` | ||
50 | + Args []ExprAST `json:"args"` | ||
51 | +} | ||
52 | + | ||
53 | +func (n NumberExprAST) toStr() string { | ||
54 | + return fmt.Sprintf( | ||
55 | + "NumberExprAST:%s", | ||
56 | + n.Str, | ||
57 | + ) | ||
43 | } | 58 | } |
44 | 59 | ||
45 | func (n ValueExprAST) toStr() string { | 60 | func (n ValueExprAST) toStr() string { |
@@ -73,9 +88,9 @@ func (n FunCallerExprAST) toStr() string { | @@ -73,9 +88,9 @@ func (n FunCallerExprAST) toStr() string { | ||
73 | } | 88 | } |
74 | 89 | ||
75 | type CloneFunCallerExprAST struct { | 90 | type CloneFunCallerExprAST struct { |
76 | - ArrayFlag bool `json:"arrayFlag"` | ||
77 | - Name string `json:"name"` | ||
78 | - Arg []json.RawMessage `json:"args"` | 91 | + //ArrayFlag bool `json:"arrayFlag"` |
92 | + Name string `json:"name"` | ||
93 | + Arg []json.RawMessage `json:"args"` | ||
79 | } | 94 | } |
80 | 95 | ||
81 | type CloneBinaryExprAST struct { | 96 | type CloneBinaryExprAST struct { |
@@ -84,7 +99,7 @@ type CloneBinaryExprAST struct { | @@ -84,7 +99,7 @@ type CloneBinaryExprAST struct { | ||
84 | Rhs json.RawMessage `json:"rhs"` | 99 | Rhs json.RawMessage `json:"rhs"` |
85 | } | 100 | } |
86 | 101 | ||
87 | -func AstExprUnmarshalJSON(data []byte) (interface{}, error) { | 102 | +func ExprUnmarshal(data []byte) (interface{}, error) { |
88 | var m = make(map[string]interface{}) | 103 | var m = make(map[string]interface{}) |
89 | if err := json.Unmarshal(data, &m); err != nil { | 104 | if err := json.Unmarshal(data, &m); err != nil { |
90 | return nil, err | 105 | return nil, err |
@@ -108,7 +123,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -108,7 +123,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
108 | } | 123 | } |
109 | exprReturn := &FunCallerExprAST{Name: expr.Name, ExprType: TypeFunCallerExprAST} | 124 | exprReturn := &FunCallerExprAST{Name: expr.Name, ExprType: TypeFunCallerExprAST} |
110 | for i := range expr.Arg { | 125 | for i := range expr.Arg { |
111 | - subExpr, err := AstExprUnmarshalJSON(expr.Arg[i]) | 126 | + subExpr, err := ExprUnmarshal(expr.Arg[i]) |
112 | if err != nil { | 127 | if err != nil { |
113 | return nil, err | 128 | return nil, err |
114 | } | 129 | } |
@@ -126,7 +141,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -126,7 +141,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
126 | } | 141 | } |
127 | exprReturn := &BinaryExprAST{Op: expr.Op, ExprType: TypeBinaryExprAST} | 142 | exprReturn := &BinaryExprAST{Op: expr.Op, ExprType: TypeBinaryExprAST} |
128 | if len(expr.Lhs) > 0 { | 143 | if len(expr.Lhs) > 0 { |
129 | - subExpr, err := AstExprUnmarshalJSON(expr.Lhs) | 144 | + subExpr, err := ExprUnmarshal(expr.Lhs) |
130 | if err != nil { | 145 | if err != nil { |
131 | return nil, err | 146 | return nil, err |
132 | } | 147 | } |
@@ -135,7 +150,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | @@ -135,7 +150,7 @@ func unmarshalMapInterface(m map[string]interface{}, rawData []byte) (interface{ | ||
135 | } | 150 | } |
136 | } | 151 | } |
137 | if len(expr.Rhs) > 0 { | 152 | if len(expr.Rhs) > 0 { |
138 | - subExpr, err := AstExprUnmarshalJSON(expr.Rhs) | 153 | + subExpr, err := ExprUnmarshal(expr.Rhs) |
139 | if err != nil { | 154 | if err != nil { |
140 | return nil, err | 155 | return nil, err |
141 | } | 156 | } |
pkg/domain/astexpr/ast_expr_builder.go
0 → 100644
1 | +package astexpr | ||
2 | + | ||
3 | +import ( | ||
4 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
5 | + "strconv" | ||
6 | + "strings" | ||
7 | +) | ||
8 | + | ||
9 | +func NewNumberExprAST(val string) NumberExprAST { | ||
10 | + num, _ := strconv.ParseFloat(val, 64) | ||
11 | + return NumberExprAST{ | ||
12 | + ExprType: TypeNumberExprAST, | ||
13 | + Val: num, | ||
14 | + Str: val, | ||
15 | + } | ||
16 | +} | ||
17 | + | ||
18 | +func NewValueExprAST(val string) ValueExprAST { | ||
19 | + return ValueExprAST{ | ||
20 | + ExprType: TypeValueExprAST, | ||
21 | + Val: val, | ||
22 | + Str: val, | ||
23 | + } | ||
24 | +} | ||
25 | + | ||
26 | +func NewFieldExprAST(val string) FieldExprAST { | ||
27 | + words := strings.Split(val, ".") | ||
28 | + table := words[0] | ||
29 | + filed := words[0] | ||
30 | + if len(words) > 0 { | ||
31 | + filed = words[1] | ||
32 | + } | ||
33 | + return FieldExprAST{ | ||
34 | + ExprType: TypeFieldExprAST, | ||
35 | + Str: val, | ||
36 | + Field: &domain.TableField{ | ||
37 | + FieldName: filed, | ||
38 | + FieldSqlName: filed, | ||
39 | + TableName: table, | ||
40 | + }, | ||
41 | + } | ||
42 | +} | ||
43 | + | ||
44 | +func NewBinaryExprAST(op string, lhs ExprAST, rhs ExprAST) BinaryExprAST { | ||
45 | + return BinaryExprAST{ | ||
46 | + ExprType: TypeBinaryExprAST, | ||
47 | + Op: op, | ||
48 | + Lhs: lhs, | ||
49 | + Rhs: rhs, | ||
50 | + } | ||
51 | +} | ||
52 | + | ||
53 | +func NewFunCallerExprAST(name string, args ...ExprAST) FunCallerExprAST { | ||
54 | + return FunCallerExprAST{ | ||
55 | + ExprType: TypeFunCallerExprAST, | ||
56 | + Name: name, | ||
57 | + Args: args, | ||
58 | + } | ||
59 | +} |
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 domain | 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 | - "testing" | 9 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" |
7 | ) | 10 | ) |
8 | 11 | ||
9 | func TestAstExprUnmarshalJSON(t *testing.T) { | 12 | func TestAstExprUnmarshalJSON(t *testing.T) { |
@@ -57,7 +60,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -57,7 +60,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
57 | &FieldExprAST{ | 60 | &FieldExprAST{ |
58 | ExprType: TypeFieldExprAST, | 61 | ExprType: TypeFieldExprAST, |
59 | Str: "业绩1", | 62 | Str: "业绩1", |
60 | - Field: &TableField{ | 63 | + Field: &domain.TableField{ |
61 | TableId: 1, | 64 | TableId: 1, |
62 | TableName: "测试ABC", | 65 | TableName: "测试ABC", |
63 | TableSqlName: "table_abc_test", | 66 | TableSqlName: "table_abc_test", |
@@ -83,7 +86,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -83,7 +86,7 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
83 | if err != nil { | 86 | if err != nil { |
84 | t.Fatal(err) | 87 | t.Fatal(err) |
85 | } | 88 | } |
86 | - v, err := AstExprUnmarshalJSON(data) | 89 | + v, err := ExprUnmarshal(data) |
87 | if err != nil { | 90 | if err != nil { |
88 | t.Fatal(err) | 91 | t.Fatal(err) |
89 | } | 92 | } |
@@ -93,3 +96,45 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | @@ -93,3 +96,45 @@ func TestAstExprUnmarshalJSON(t *testing.T) { | ||
93 | assert.Equal(t, input.data, v) | 96 | assert.Equal(t, input.data, v) |
94 | } | 97 | } |
95 | } | 98 | } |
99 | + | ||
100 | +func TestAstExprParse(t *testing.T) { | ||
101 | + funs := []struct { | ||
102 | + Name string | ||
103 | + Exp []string | ||
104 | + Debug bool | ||
105 | + }{ | ||
106 | + { | ||
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, | ||
121 | + }, | ||
122 | + } | ||
123 | + for _, f := range funs { | ||
124 | + if !f.Debug { | ||
125 | + continue | ||
126 | + } | ||
127 | + fmt.Println("测试项目", f.Name) | ||
128 | + fmt.Println() | ||
129 | + for _, exp := range f.Exp { | ||
130 | + fmt.Println("表达式:", exp) | ||
131 | + r, err := Parse(exp) | ||
132 | + if err != nil { | ||
133 | + t.Error(err) | ||
134 | + } | ||
135 | + if r != 0 { | ||
136 | + | ||
137 | + } | ||
138 | + } | ||
139 | + } | ||
140 | +} |
pkg/domain/astexpr/ast_parser.go
0 → 100644
1 | +package astexpr | ||
2 | + | ||
3 | +import ( | ||
4 | + "errors" | ||
5 | + "fmt" | ||
6 | + "strings" | ||
7 | +) | ||
8 | + | ||
9 | +var precedence = map[string]int{ | ||
10 | + "+": 20, "-": 20, "*": 40, "/": 40, "%": 40, "^": 60, | ||
11 | + "=": 10, ">": 10, "<": 10, "<=": 10, ">=": 10, "&": 40, | ||
12 | +} | ||
13 | + | ||
14 | +const ( | ||
15 | + // Identifier 标识符 e.g.函数名、表字段 | ||
16 | + Identifier = iota | ||
17 | + // Literal 文字 e.g. 50 | ||
18 | + Literal | ||
19 | + // Operator 计算操作 e.g. + - * / | ||
20 | + Operator | ||
21 | + // COMMA 命令, e.g. ( | ||
22 | + COMMA | ||
23 | + // CompareOperator 比较操作 e.g. < = > | ||
24 | + CompareOperator | ||
25 | + // StringArgs 字符串参数 | ||
26 | + StringArgs | ||
27 | +) | ||
28 | + | ||
29 | +type Token struct { | ||
30 | + // raw characters | ||
31 | + Tok string | ||
32 | + // type with Literal/Operator | ||
33 | + Type, | ||
34 | + Flag int | ||
35 | + | ||
36 | + Offset int | ||
37 | +} | ||
38 | + | ||
39 | +type Parser struct { | ||
40 | + Source string | ||
41 | + SourceRunes []rune | ||
42 | + ch rune | ||
43 | + offset int | ||
44 | + | ||
45 | + err error | ||
46 | +} | ||
47 | + | ||
48 | +func ParseToken(s string) ([]*Token, error) { | ||
49 | + p := &Parser{ | ||
50 | + Source: s, | ||
51 | + SourceRunes: []rune(s), | ||
52 | + err: nil, | ||
53 | + //ch: s[0], | ||
54 | + } | ||
55 | + p.ch = p.SourceRunes[0] | ||
56 | + toks := p.parse() | ||
57 | + if p.err != nil { | ||
58 | + return nil, p.err | ||
59 | + } | ||
60 | + return toks, nil | ||
61 | +} | ||
62 | + | ||
63 | +func (p *Parser) parse() []*Token { | ||
64 | + toks := make([]*Token, 0) | ||
65 | + for { | ||
66 | + tok := p.nextTok() | ||
67 | + if tok == nil { | ||
68 | + break | ||
69 | + } | ||
70 | + toks = append(toks, tok) | ||
71 | + } | ||
72 | + return toks | ||
73 | +} | ||
74 | + | ||
75 | +func (p *Parser) nextTok() *Token { | ||
76 | + if p.offset >= len(p.SourceRunes) || p.err != nil { | ||
77 | + return nil | ||
78 | + } | ||
79 | + var err error | ||
80 | + for p.isWhitespace(p.ch) && err == nil { | ||
81 | + err = p.nextCh() | ||
82 | + } | ||
83 | + start := p.offset | ||
84 | + var tok *Token | ||
85 | + switch p.ch { | ||
86 | + case | ||
87 | + '(', | ||
88 | + ')', | ||
89 | + '+', | ||
90 | + '-', | ||
91 | + '*', | ||
92 | + '/', | ||
93 | + '^', | ||
94 | + '%', | ||
95 | + '&': | ||
96 | + tok = &Token{ | ||
97 | + Tok: string(p.ch), | ||
98 | + Type: Operator, | ||
99 | + } | ||
100 | + tok.Offset = start | ||
101 | + err = p.nextCh() | ||
102 | + case | ||
103 | + '>', | ||
104 | + '<', | ||
105 | + '=': | ||
106 | + if p.isCompareWordChar(p.ch) { | ||
107 | + for p.isCompareWordChar(p.ch) && p.nextCh() == nil { | ||
108 | + } | ||
109 | + tok = &Token{ | ||
110 | + Tok: string(p.SourceRunes[start:p.offset]), | ||
111 | + Type: CompareOperator, | ||
112 | + } | ||
113 | + tok.Offset = start | ||
114 | + } else if p.ch != ' ' { | ||
115 | + s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s", | ||
116 | + string(p.ch), | ||
117 | + start, | ||
118 | + ErrPos(p.Source, start)) | ||
119 | + p.err = errors.New(s) | ||
120 | + } | ||
121 | + case | ||
122 | + '0', | ||
123 | + '1', | ||
124 | + '2', | ||
125 | + '3', | ||
126 | + '4', | ||
127 | + '5', | ||
128 | + '6', | ||
129 | + '7', | ||
130 | + '8', | ||
131 | + '9': | ||
132 | + for p.isDigitNum(p.ch) && p.nextCh() == nil { | ||
133 | + if (p.ch == '-' || p.ch == '+') && p.SourceRunes[p.offset-1] != 'e' { | ||
134 | + break | ||
135 | + } | ||
136 | + } | ||
137 | + tok = &Token{ | ||
138 | + Tok: strings.ReplaceAll(string(p.SourceRunes[start:p.offset]), "_", ""), | ||
139 | + Type: Literal, | ||
140 | + } | ||
141 | + tok.Offset = start | ||
142 | + case '"': | ||
143 | + for (p.isDigitNum(p.ch) || p.isChar(p.ch) || p.isCompareWordChar(p.ch) || p.ch == '*') && p.nextCh() == nil { | ||
144 | + if p.ch == '"' { | ||
145 | + break | ||
146 | + } | ||
147 | + } | ||
148 | + err = p.nextCh() | ||
149 | + tok = &Token{ | ||
150 | + Tok: string(p.SourceRunes[start:p.offset]), | ||
151 | + Type: StringArgs, | ||
152 | + } | ||
153 | + tok.Offset = start | ||
154 | + case ',': | ||
155 | + tok = &Token{ | ||
156 | + Tok: string(p.ch), | ||
157 | + Type: COMMA, | ||
158 | + } | ||
159 | + tok.Offset = start | ||
160 | + err = p.nextCh() | ||
161 | + | ||
162 | + default: | ||
163 | + if p.isChar(p.ch) { | ||
164 | + for p.isWordChar(p.ch) && p.nextCh() == nil { | ||
165 | + } | ||
166 | + tok = &Token{ | ||
167 | + Tok: string(p.SourceRunes[start:p.offset]), | ||
168 | + Type: Identifier, | ||
169 | + } | ||
170 | + tok.Offset = start | ||
171 | + } else if p.ch != ' ' { | ||
172 | + s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s", | ||
173 | + string(p.ch), | ||
174 | + start, | ||
175 | + ErrPos(p.Source, start)) | ||
176 | + p.err = errors.New(s) | ||
177 | + } | ||
178 | + } | ||
179 | + return tok | ||
180 | +} | ||
181 | + | ||
182 | +func (p *Parser) nextCh() error { | ||
183 | + p.offset++ | ||
184 | + if p.offset < len(p.SourceRunes) { | ||
185 | + p.ch = p.SourceRunes[p.offset] | ||
186 | + return nil | ||
187 | + } | ||
188 | + return errors.New("EOF") | ||
189 | +} | ||
190 | + | ||
191 | +func (p *Parser) isWhitespace(c rune) bool { | ||
192 | + return c == ' ' || | ||
193 | + c == '\t' || | ||
194 | + c == '\n' || | ||
195 | + c == '\v' || | ||
196 | + c == '\f' || | ||
197 | + c == '\r' | ||
198 | +} | ||
199 | + | ||
200 | +func (p *Parser) isDigitNum(c rune) bool { | ||
201 | + return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+' | ||
202 | +} | ||
203 | + | ||
204 | +func (p *Parser) isChar(c rune) bool { | ||
205 | + return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '.' || c == '"' || isChineseCharacter(c) | ||
206 | + //判断是汉字 | ||
207 | +} | ||
208 | + | ||
209 | +func (p *Parser) isWordChar(c rune) bool { | ||
210 | + return p.isChar(c) || '0' <= c && c <= '9' | ||
211 | +} | ||
212 | + | ||
213 | +func (p *Parser) isCompareWordChar(c rune) bool { | ||
214 | + return c == '=' || c == '<' || c == '>' | ||
215 | +} | ||
216 | + | ||
217 | +func isChineseCharacter(c rune) bool { | ||
218 | + return len([]byte(string(c))) > 2 | ||
219 | +} |
pkg/domain/astexpr/ast_test.go
0 → 100644
1 | +package astexpr | ||
2 | + | ||
3 | +import ( | ||
4 | + "bytes" | ||
5 | + "encoding/json" | ||
6 | + "fmt" | ||
7 | + "testing" | ||
8 | +) | ||
9 | + | ||
10 | +func TestExecA(t *testing.T) { | ||
11 | + exp := "1+2" | ||
12 | + exec(exp) | ||
13 | +} | ||
14 | + | ||
15 | +func TestExecB(t *testing.T) { | ||
16 | + exp := "1+2-4" | ||
17 | + exec(exp) | ||
18 | +} | ||
19 | + | ||
20 | +func TestExecC(t *testing.T) { | ||
21 | + exp := "1+2-4*3-8" | ||
22 | + exec(exp) | ||
23 | +} | ||
24 | + | ||
25 | +func TestExecD(t *testing.T) { | ||
26 | + exp := "1+2-(4*3-8)" | ||
27 | + exec(exp) | ||
28 | +} | ||
29 | + | ||
30 | +func TestExecE(t *testing.T) { | ||
31 | + exp := "1+2-(4*3+(1-8))" | ||
32 | + exec(exp) | ||
33 | +} | ||
34 | + | ||
35 | +func TestExecF(t *testing.T) { | ||
36 | + exp := "1+(2-(4*3+(1-8)))" | ||
37 | + exec(exp) | ||
38 | +} | ||
39 | + | ||
40 | +func TestExecG(t *testing.T) { | ||
41 | + exp := "((1-2)*(3-8))*((((9+2222))))" | ||
42 | + exec(exp) | ||
43 | +} | ||
44 | + | ||
45 | +func TestExecH(t *testing.T) { | ||
46 | + exp := "0.8888-0.1 * 444 -0.2" | ||
47 | + exec(exp) | ||
48 | +} | ||
49 | + | ||
50 | +func TestExecI(t *testing.T) { | ||
51 | + exp := "0.8888-0.1 * (444 -0.2)" | ||
52 | + exec(exp) | ||
53 | +} | ||
54 | + | ||
55 | +func TestExecJ(t *testing.T) { | ||
56 | + exp := "1_234_567*2-3" | ||
57 | + exec(exp) | ||
58 | +} | ||
59 | + | ||
60 | +func TestExecK(t *testing.T) { | ||
61 | + exp := "2.3e4*4/3" | ||
62 | + exec(exp) | ||
63 | +} | ||
64 | + | ||
65 | +func TestExecL(t *testing.T) { | ||
66 | + exp := "-1+9-88" | ||
67 | + exec(exp) | ||
68 | +} | ||
69 | + | ||
70 | +func TestExecM(t *testing.T) { | ||
71 | + exp := "-1+9-88+(88)" | ||
72 | + exec(exp) | ||
73 | +} | ||
74 | + | ||
75 | +func TestExecN(t *testing.T) { | ||
76 | + exp := "-1+9-88+(-88)*666-1" | ||
77 | + exec(exp) | ||
78 | +} | ||
79 | + | ||
80 | +func TestExecO(t *testing.T) { | ||
81 | + exp := "-(1)+(3)-(-3)*7-((-3))" | ||
82 | + exec(exp) | ||
83 | +} | ||
84 | + | ||
85 | +func TestExecP(t *testing.T) { | ||
86 | + exp := "-(-9+3)" | ||
87 | + exec(exp) | ||
88 | +} | ||
89 | + | ||
90 | +func TestExecQ(t *testing.T) { | ||
91 | + exp := "2e-3*2+2e2+1" | ||
92 | + exec(exp) | ||
93 | +} | ||
94 | + | ||
95 | +func TestExecR(t *testing.T) { | ||
96 | + exp := "3.8 - 56 / (1-1) - 4" | ||
97 | + exec(exp) | ||
98 | +} | ||
99 | + | ||
100 | +func TestExecS(t *testing.T) { | ||
101 | + exp := "noerr(3.8 - 56 / (1-1) - 4)" | ||
102 | + exec(exp) | ||
103 | +} | ||
104 | + | ||
105 | +func TestFunCaller(t *testing.T) { | ||
106 | + funs := []struct { | ||
107 | + Name string | ||
108 | + Argc int | ||
109 | + Fun func(expr ...ExprAST) float64 | ||
110 | + Exp string | ||
111 | + R float64 | ||
112 | + }{ | ||
113 | + //{ | ||
114 | + // "double", | ||
115 | + // 1, | ||
116 | + // func(expr ...engine.ExprAST) float64 { | ||
117 | + // return engine.ExprASTResult(expr[0]) * 2 | ||
118 | + // }, | ||
119 | + // "double(6)", | ||
120 | + // 12, | ||
121 | + //}, | ||
122 | + { | ||
123 | + "sum", | ||
124 | + -1, | ||
125 | + nil, | ||
126 | + "sum(if(100+10,table.a,20))", | ||
127 | + 10, | ||
128 | + }, | ||
129 | + { | ||
130 | + "sum", | ||
131 | + -1, | ||
132 | + nil, | ||
133 | + "sum(if(100<10,table.a,20))", | ||
134 | + 10, | ||
135 | + }, | ||
136 | + { | ||
137 | + "sum", | ||
138 | + -1, | ||
139 | + nil, | ||
140 | + "sum(if(100<10,table.a,20+30))", | ||
141 | + 10, | ||
142 | + }, | ||
143 | + { | ||
144 | + "sum", | ||
145 | + -1, | ||
146 | + nil, | ||
147 | + "sum(if(table.a<table.b,table.a,20+30))", | ||
148 | + 10, | ||
149 | + }, | ||
150 | + { | ||
151 | + "sum", | ||
152 | + -1, | ||
153 | + nil, | ||
154 | + "sum(if(table.a<=table.b,table.a,20+30))", | ||
155 | + 10, | ||
156 | + }, | ||
157 | + } | ||
158 | + for _, f := range funs { | ||
159 | + if f.Fun != nil { | ||
160 | + _ = RegFunction(f.Name, f.Argc, f.Fun) | ||
161 | + } | ||
162 | + r, err := Parse(f.Exp) | ||
163 | + if err != nil { | ||
164 | + | ||
165 | + } | ||
166 | + if r != 0 { | ||
167 | + | ||
168 | + } | ||
169 | + } | ||
170 | +} | ||
171 | + | ||
172 | +func TestFunCaller2(t *testing.T) { | ||
173 | + funs := []struct { | ||
174 | + Name string | ||
175 | + Exp []string | ||
176 | + }{ | ||
177 | + { | ||
178 | + "sum", | ||
179 | + []string{"sum(table.a)"}, | ||
180 | + }, | ||
181 | + { | ||
182 | + "sumif", | ||
183 | + []string{"sumif(table.month,10,table.count)"}, | ||
184 | + }, | ||
185 | + { | ||
186 | + "if", | ||
187 | + []string{"if(table.month>10,table.count1,table.count2)"}, | ||
188 | + }, | ||
189 | + { | ||
190 | + "and", | ||
191 | + []string{"and(table.year=2011,table.month=6)"}, | ||
192 | + }, | ||
193 | + { | ||
194 | + "or", | ||
195 | + []string{"or(table.year=2011,table.year=2012)"}, | ||
196 | + }, | ||
197 | + { | ||
198 | + "month", | ||
199 | + []string{"month(\"1991-1-1\")"}, | ||
200 | + }, | ||
201 | + { | ||
202 | + "year", | ||
203 | + []string{"year(\"1991-1-1\")"}, | ||
204 | + }, | ||
205 | + { | ||
206 | + "round", | ||
207 | + []string{ | ||
208 | + "round(1.56)", | ||
209 | + "round(table.a)", | ||
210 | + }, | ||
211 | + }, | ||
212 | + { | ||
213 | + "rounddown", | ||
214 | + []string{ | ||
215 | + "rounddown(1.56)", | ||
216 | + "rounddown(table.a)", | ||
217 | + }, | ||
218 | + }, | ||
219 | + { | ||
220 | + "roundup", | ||
221 | + []string{ | ||
222 | + "roundup(1.56)", | ||
223 | + "roundup(table.a)", | ||
224 | + }, | ||
225 | + }, | ||
226 | + { | ||
227 | + "count", | ||
228 | + []string{ | ||
229 | + "count(1.56)", | ||
230 | + "count(table.a)", | ||
231 | + }, | ||
232 | + }, | ||
233 | + { | ||
234 | + "&", | ||
235 | + []string{ | ||
236 | + "table.a&table.b", | ||
237 | + }, | ||
238 | + }, | ||
239 | + } | ||
240 | + for _, f := range funs { | ||
241 | + for _, exp := range f.Exp { | ||
242 | + r, err := Parse(exp) | ||
243 | + if err != nil { | ||
244 | + t.Error(err) | ||
245 | + } | ||
246 | + if r != 0 { | ||
247 | + | ||
248 | + } | ||
249 | + } | ||
250 | + } | ||
251 | +} | ||
252 | + | ||
253 | +func Parse(s string) (r float64, err error) { | ||
254 | + toks, err := ParseToken(s) | ||
255 | + if err != nil { | ||
256 | + return 0, err | ||
257 | + } | ||
258 | + ast := NewAST(toks, s) | ||
259 | + if ast.Err != nil { | ||
260 | + return 0, ast.Err | ||
261 | + } | ||
262 | + ar := ast.ParseExpression() | ||
263 | + if ast.Err != nil { | ||
264 | + return 0, ast.Err | ||
265 | + } | ||
266 | + defer func() { | ||
267 | + if e := recover(); e != nil { | ||
268 | + err = e.(error) | ||
269 | + } | ||
270 | + }() | ||
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()) | ||
279 | + return 0, err | ||
280 | +} | ||
281 | + | ||
282 | +// call engine | ||
283 | +func exec(exp string) { | ||
284 | + // input text -> []token | ||
285 | + toks, err := ParseToken(exp) | ||
286 | + if err != nil { | ||
287 | + fmt.Println("ERROR: " + err.Error()) | ||
288 | + return | ||
289 | + } | ||
290 | + | ||
291 | + // []token -> AST Tree | ||
292 | + ast := NewAST(toks, exp) | ||
293 | + if ast.Err != nil { | ||
294 | + fmt.Println("ERROR: " + ast.Err.Error()) | ||
295 | + return | ||
296 | + } | ||
297 | + // AST builder | ||
298 | + ar := ast.ParseExpression() | ||
299 | + if ast.Err != nil { | ||
300 | + fmt.Println("ERROR: " + ast.Err.Error()) | ||
301 | + return | ||
302 | + } | ||
303 | + fmt.Printf("ExprAST: %+v\n", ar) | ||
304 | + // catch runtime errors | ||
305 | + defer func() { | ||
306 | + if e := recover(); e != nil { | ||
307 | + fmt.Println("ERROR: ", e) | ||
308 | + } | ||
309 | + }() | ||
310 | + // AST traversal -> result | ||
311 | + r := ExprASTResult(ar) | ||
312 | + fmt.Println("progressing ...\t", r) | ||
313 | + fmt.Printf("%s = %v\n", exp, r) | ||
314 | +} |
pkg/domain/astexpr/ast_util.go
0 → 100644
1 | +package astexpr | ||
2 | + | ||
3 | +import ( | ||
4 | + "errors" | ||
5 | + "fmt" | ||
6 | + "math" | ||
7 | + "math/big" | ||
8 | + "strconv" | ||
9 | + "strings" | ||
10 | +) | ||
11 | + | ||
12 | +// Top level function | ||
13 | +// Analytical expression and execution | ||
14 | +// err is not nil if an error occurs (including arithmetic runtime errors) | ||
15 | +func ParseAndExec(s string) (r float64, err error) { | ||
16 | + toks, err := ParseToken(s) | ||
17 | + if err != nil { | ||
18 | + return 0, err | ||
19 | + } | ||
20 | + ast := NewAST(toks, s) | ||
21 | + if ast.Err != nil { | ||
22 | + return 0, ast.Err | ||
23 | + } | ||
24 | + ar := ast.ParseExpression() | ||
25 | + if ast.Err != nil { | ||
26 | + return 0, ast.Err | ||
27 | + } | ||
28 | + defer func() { | ||
29 | + if e := recover(); e != nil { | ||
30 | + err = e.(error) | ||
31 | + } | ||
32 | + }() | ||
33 | + return ExprASTResult(ar), err | ||
34 | +} | ||
35 | + | ||
36 | +func ErrPos(s string, pos int) string { | ||
37 | + r := strings.Repeat("-", len(s)) + "\n" | ||
38 | + s += "\n" | ||
39 | + for i := 0; i < pos; i++ { | ||
40 | + s += " " | ||
41 | + } | ||
42 | + s += "^\n" | ||
43 | + return r + s + r | ||
44 | +} | ||
45 | + | ||
46 | +// the integer power of a number | ||
47 | +func Pow(x float64, n float64) float64 { | ||
48 | + return math.Pow(x, n) | ||
49 | +} | ||
50 | + | ||
51 | +func expr2Radian(expr ExprAST) float64 { | ||
52 | + r := ExprASTResult(expr) | ||
53 | + if TrigonometricMode == AngleMode { | ||
54 | + r = r / 180 * math.Pi | ||
55 | + } | ||
56 | + return r | ||
57 | +} | ||
58 | + | ||
59 | +// Float64ToStr float64 -> string | ||
60 | +func Float64ToStr(f float64) string { | ||
61 | + return strconv.FormatFloat(f, 'f', -1, 64) | ||
62 | +} | ||
63 | + | ||
64 | +// RegFunction is Top level function | ||
65 | +// register a new function to use in expressions | ||
66 | +// name: be register function name. the same function name only needs to be registered once. | ||
67 | +// argc: this is a number of parameter signatures. should be -1, 0, or a positive integer | ||
68 | +// | ||
69 | +// -1 variable-length argument; >=0 fixed numbers argument | ||
70 | +// | ||
71 | +// fun: function handler | ||
72 | +func RegFunction(name string, argc int, fun func(...ExprAST) float64) error { | ||
73 | + if len(name) == 0 { | ||
74 | + return errors.New("RegFunction name is not empty") | ||
75 | + } | ||
76 | + if argc < -1 { | ||
77 | + return errors.New("RegFunction argc should be -1, 0, or a positive integer") | ||
78 | + } | ||
79 | + if _, ok := defFunc[name]; ok { | ||
80 | + return errors.New("RegFunction name is already exist") | ||
81 | + } | ||
82 | + defFunc[name] = defS{argc, fun} | ||
83 | + return nil | ||
84 | +} | ||
85 | + | ||
86 | +// ExprASTResult is a Top level function | ||
87 | +// AST traversal | ||
88 | +// if an arithmetic runtime error occurs, a panic exception is thrown | ||
89 | +func ExprASTResult(expr ExprAST) float64 { | ||
90 | + var l, r float64 | ||
91 | + switch expr.(type) { | ||
92 | + case BinaryExprAST: | ||
93 | + ast := expr.(BinaryExprAST) | ||
94 | + l = ExprASTResult(ast.Lhs) | ||
95 | + r = ExprASTResult(ast.Rhs) | ||
96 | + switch ast.Op { | ||
97 | + case "+": | ||
98 | + lh, _ := new(big.Float).SetString(Float64ToStr(l)) | ||
99 | + rh, _ := new(big.Float).SetString(Float64ToStr(r)) | ||
100 | + f, _ := new(big.Float).Add(lh, rh).Float64() | ||
101 | + return f | ||
102 | + case "-": | ||
103 | + lh, _ := new(big.Float).SetString(Float64ToStr(l)) | ||
104 | + rh, _ := new(big.Float).SetString(Float64ToStr(r)) | ||
105 | + f, _ := new(big.Float).Sub(lh, rh).Float64() | ||
106 | + return f | ||
107 | + case "*": | ||
108 | + f, _ := new(big.Float).Mul(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64() | ||
109 | + return f | ||
110 | + case "/": | ||
111 | + if r == 0 { | ||
112 | + panic(errors.New( | ||
113 | + fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g/%g]", | ||
114 | + l, | ||
115 | + r))) | ||
116 | + } | ||
117 | + f, _ := new(big.Float).Quo(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64() | ||
118 | + return f | ||
119 | + case "%": | ||
120 | + if r == 0 { | ||
121 | + panic(errors.New( | ||
122 | + fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g%%%g]", | ||
123 | + l, | ||
124 | + r))) | ||
125 | + } | ||
126 | + return float64(int(l) % int(r)) | ||
127 | + case "^": | ||
128 | + return Pow(l, r) | ||
129 | + default: | ||
130 | + | ||
131 | + } | ||
132 | + case NumberExprAST: | ||
133 | + return expr.(NumberExprAST).Val | ||
134 | + case FunCallerExprAST: | ||
135 | + f := expr.(FunCallerExprAST) | ||
136 | + def := defFunc[f.Name] | ||
137 | + return def.fun(f.Args...) | ||
138 | + } | ||
139 | + | ||
140 | + return 0.0 | ||
141 | +} |
pkg/domain/astexpr/ast_util_test.go
0 → 100644
1 | +package astexpr | ||
2 | + | ||
3 | +import ( | ||
4 | + "math/rand" | ||
5 | + "testing" | ||
6 | + "time" | ||
7 | +) | ||
8 | + | ||
9 | +func TestParseAndExecSimple(t *testing.T) { | ||
10 | + type U struct { | ||
11 | + Expr string | ||
12 | + R float64 | ||
13 | + } | ||
14 | + exprs := []U{ | ||
15 | + {"1", 1}, | ||
16 | + {"--1", 1}, | ||
17 | + {"1+2", 3}, | ||
18 | + {"-1+2", 1}, | ||
19 | + {"-(1+2)", -3}, | ||
20 | + {"-(1+2)*5", -15}, | ||
21 | + {"-(1+2)*5/3", -5}, | ||
22 | + {"1+(-(1+2)*5/3)", -4}, | ||
23 | + {"3^4", 81}, | ||
24 | + {"3^4.5", 140.29611541307906}, | ||
25 | + {"3.5^4.5", 280.7412308013823}, | ||
26 | + {"8%2", 0}, | ||
27 | + {"8%3", 2}, | ||
28 | + {"8%3.5", 2}, | ||
29 | + {"1e2", 100}, | ||
30 | + {"1e+2", 100}, | ||
31 | + {"1e-2", 0.01}, | ||
32 | + {"1e-2+1e2", 100.01}, | ||
33 | + {"1e-2+1e2*6/3", 200.01}, | ||
34 | + {"(1e-2+1e2)*6/3", 200.02}, | ||
35 | + {"(88*8)+(1+1+1+1)+(6/1.5)-(99%9*(2^4))", 712}, | ||
36 | + {"1/3*3", 1}, | ||
37 | + {"123_456_789", 123456789}, | ||
38 | + {"123_456_789___", 123456789}, | ||
39 | + {"pi", 3.141592653589793}, | ||
40 | + {"abs(1)", 1}, | ||
41 | + {"abs(-1)", 1}, | ||
42 | + {"ceil(90.2)", 91}, | ||
43 | + {"ceil(90.8)", 91}, | ||
44 | + {"ceil(90.0)", 90}, | ||
45 | + {"floor(90.2)", 90}, | ||
46 | + {"floor(90.8)", 90}, | ||
47 | + {"floor(90.0)", 90}, | ||
48 | + {"round(90.0)", 90}, | ||
49 | + {"round(90.4)", 90}, | ||
50 | + {"round(90.5)", 91}, | ||
51 | + {"round(90.9)", 91}, | ||
52 | + {"sqrt(4)", 2}, | ||
53 | + {"cbrt(27)", 3}, | ||
54 | + {"sqrt(4) + cbrt(27)", 5}, | ||
55 | + {"sqrt(2^2) + cbrt(3^3)", 5}, | ||
56 | + {"127^2+5/2-sqrt(2^2) + cbrt(3^3)", 16132.5}, | ||
57 | + {"max(2)", 2}, | ||
58 | + {"max(abs(1)+10)", 11}, | ||
59 | + {"max(abs(1)+10)*2-1", 21}, | ||
60 | + {"max(2,3.5)", 3.5}, | ||
61 | + {"max(2^3,3+abs(-1)*6)", 9}, | ||
62 | + {"max(2^3,3+abs(-1)*6, 20)", 20}, | ||
63 | + {"max(2^3,3+abs(-1)*6,ceil(9.4))", 10}, | ||
64 | + {"max(1,2,3,4,5,6,10,7,4,5,6,9.8)", 10}, | ||
65 | + {"min(3.5)", 3.5}, | ||
66 | + {"min(ceil(1.2))", 2}, | ||
67 | + {"min(2,3.5)", 2}, | ||
68 | + {"min(2^3,3+abs(-1)*6)", 8}, | ||
69 | + {"min(2^3,3+abs(-1)*6,1^10)", 1}, | ||
70 | + {"min(99.1,0.2,3,4,5,6,10,7,4,5,6,9.8)", 0.2}, | ||
71 | + {"max(2^3,3^2)", 9}, | ||
72 | + {"min(2^3,3^2)", 8}, | ||
73 | + {"noerr(1/0)", 0}, | ||
74 | + {"noerr(1/(1-1))", 0}, | ||
75 | + {"0.1+0.2", 0.3}, | ||
76 | + {"0.3-0.1", 0.2}, | ||
77 | + {"10^-1", 0.1}, | ||
78 | + {"10^-2", 0.01}, | ||
79 | + {"10^-1*100", 10}, | ||
80 | + {"10%0", 0}, | ||
81 | + } | ||
82 | + for _, e := range exprs { | ||
83 | + r, _ := ParseAndExec(e.Expr) | ||
84 | + if r != e.R { | ||
85 | + t.Error(e, " ParseAndExec:", r) | ||
86 | + } | ||
87 | + } | ||
88 | +} | ||
89 | + | ||
90 | +func TestParseAndExecTrigonometric(t *testing.T) { | ||
91 | + type U struct { | ||
92 | + Expr string | ||
93 | + RadianMode float64 | ||
94 | + AngleMode float64 | ||
95 | + } | ||
96 | + exprs := []U{ | ||
97 | + {"sin(pi/2)", 1, 0.027412133592044294}, | ||
98 | + {"csc(pi/2)", 1, 36.48019577324057}, | ||
99 | + {"cos(0)", 1, 1}, | ||
100 | + {"sec(0)", 1, 1}, | ||
101 | + {"tan(pi/4)", 1, 0.013708642534394057}, | ||
102 | + {"cot(pi/4)", 1, 72.94668290394674}, | ||
103 | + | ||
104 | + {"sin(90)", 0.893996663600558, 1}, | ||
105 | + {"csc(90)", 1.1185724071637082, 1}, | ||
106 | + {"cos(0)", 1, 1}, | ||
107 | + {"sec(0)", 1, 1}, | ||
108 | + {"tan(45)", 1.6197751905438615, 1}, | ||
109 | + {"cot(45)", 0.6173696237835551, 1}, | ||
110 | + } | ||
111 | + for _, e := range exprs { | ||
112 | + TrigonometricMode = RadianMode | ||
113 | + r, _ := ParseAndExec(e.Expr) | ||
114 | + if r != e.RadianMode { | ||
115 | + t.Error(e, " ParseAndExec RadianMode:", r) | ||
116 | + } | ||
117 | + TrigonometricMode = AngleMode | ||
118 | + r, _ = ParseAndExec(e.Expr) | ||
119 | + if r != e.AngleMode { | ||
120 | + t.Error(e, " ParseAndExec AngleMode:", r) | ||
121 | + } | ||
122 | + } | ||
123 | +} | ||
124 | + | ||
125 | +func TestRegFunction(t *testing.T) { | ||
126 | + funs := []struct { | ||
127 | + Name string | ||
128 | + Argc int | ||
129 | + Fun func(expr ...ExprAST) float64 | ||
130 | + Exp string | ||
131 | + R float64 | ||
132 | + }{ | ||
133 | + { | ||
134 | + "double", | ||
135 | + 1, | ||
136 | + func(expr ...ExprAST) float64 { | ||
137 | + return ExprASTResult(expr[0]) * 2 | ||
138 | + }, | ||
139 | + "double(6)", | ||
140 | + 12, | ||
141 | + }, | ||
142 | + { | ||
143 | + "percentage50", | ||
144 | + 1, | ||
145 | + func(expr ...ExprAST) float64 { | ||
146 | + return ExprASTResult(expr[0]) / 2 | ||
147 | + }, | ||
148 | + "percentage50(6)", | ||
149 | + 3, | ||
150 | + }, | ||
151 | + { | ||
152 | + "range", | ||
153 | + 0, | ||
154 | + func(expr ...ExprAST) float64 { | ||
155 | + return 10.0 | ||
156 | + }, | ||
157 | + "range()", | ||
158 | + 10, | ||
159 | + }, | ||
160 | + { | ||
161 | + "choice", | ||
162 | + -1, | ||
163 | + func(expr ...ExprAST) float64 { | ||
164 | + rand.Seed(time.Now().UnixNano()) | ||
165 | + return ExprASTResult(expr[rand.Intn(len(expr))]) | ||
166 | + }, | ||
167 | + "choice(1.1, 9.8, 2.5, 100)", | ||
168 | + 10, | ||
169 | + }, | ||
170 | + } | ||
171 | + for _, f := range funs { | ||
172 | + _ = RegFunction(f.Name, f.Argc, f.Fun) | ||
173 | + r, err := ParseAndExec(f.Exp) | ||
174 | + if f.Name == "choice" { | ||
175 | + if !inSlices(r, []float64{1.1, 9.8, 2.5, 100}) { | ||
176 | + t.Error(err, "RegFunction errors when register new function: ", f.Name) | ||
177 | + } | ||
178 | + continue | ||
179 | + } else if r != f.R { | ||
180 | + t.Error(err, "RegFunction errors when register new function: ", f.Name) | ||
181 | + } | ||
182 | + } | ||
183 | + | ||
184 | +} | ||
185 | + | ||
186 | +func TestParseAndExecError(t *testing.T) { | ||
187 | + exprs := []string{ | ||
188 | + "(", | ||
189 | + "((((((", | ||
190 | + "((xscdfddff", | ||
191 | + "(1", | ||
192 | + "(1+", | ||
193 | + "1+", | ||
194 | + "1*", | ||
195 | + "+2344", | ||
196 | + "3+(", | ||
197 | + "4+(90-", | ||
198 | + "3-(4*7-2)+", | ||
199 | + "3-(4*7-2)+98*", | ||
200 | + "1#1", | ||
201 | + "_123_456_789___", | ||
202 | + "1ee3+3", | ||
203 | + "sin()", | ||
204 | + "sin", | ||
205 | + "pi(", | ||
206 | + "sin(1, 50)", | ||
207 | + "max", | ||
208 | + "max()", | ||
209 | + "max(1,)", | ||
210 | + "max(1,4,6,7,5,)", | ||
211 | + "min", | ||
212 | + "min(,)", | ||
213 | + "min()", | ||
214 | + "min(1,)", | ||
215 | + "min(1,998,4,23,234,2,)", | ||
216 | + "min(1,998,4,23,234,2,,,)", | ||
217 | + "1/0", | ||
218 | + "99.9 / (2-1-1)", | ||
219 | + "(1+2)3", | ||
220 | + "1+1 111", | ||
221 | + "1+1 111+2", | ||
222 | + "1 3", | ||
223 | + "1 3-", | ||
224 | + } | ||
225 | + for _, e := range exprs { | ||
226 | + _, err := ParseAndExec(e) | ||
227 | + if err == nil { | ||
228 | + t.Error(e, " this is error expr!") | ||
229 | + } | ||
230 | + } | ||
231 | +} | ||
232 | + | ||
233 | +func inSlices(target float64, s []float64) bool { | ||
234 | + for _, v := range s { | ||
235 | + if v == target { | ||
236 | + return true | ||
237 | + } | ||
238 | + } | ||
239 | + return false | ||
240 | +} |
@@ -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 { |
@@ -28,6 +28,16 @@ type Field struct { | @@ -28,6 +28,16 @@ type Field struct { | ||
28 | Order string `json:"order,omitempty"` | 28 | Order string `json:"order,omitempty"` |
29 | } | 29 | } |
30 | 30 | ||
31 | +func (f *Field) SqlTypeEqual(compare *Field) bool { | ||
32 | + if f.SQLType == compare.SQLType { | ||
33 | + return true | ||
34 | + } | ||
35 | + if SQLType(f.SQLType).IsString() == SQLType(compare.SQLType).IsString() { | ||
36 | + return true | ||
37 | + } | ||
38 | + return false | ||
39 | +} | ||
40 | + | ||
31 | func (f *Field) Valid() error { | 41 | func (f *Field) Valid() error { |
32 | if _, ok := SQLTypeMap[strings.ToUpper(f.SQLType)]; !ok { | 42 | if _, ok := SQLTypeMap[strings.ToUpper(f.SQLType)]; !ok { |
33 | return fmt.Errorf("unknown sql type:%v", f.SQLType) | 43 | return fmt.Errorf("unknown sql type:%v", f.SQLType) |
@@ -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 { |
@@ -67,7 +67,7 @@ func (table *Table) TableIdString() string { | @@ -67,7 +67,7 @@ func (table *Table) TableIdString() string { | ||
67 | 67 | ||
68 | func (table *Table) WithContext(ctx *Context) *Table { | 68 | func (table *Table) WithContext(ctx *Context) *Table { |
69 | rand.Seed(time.Now().Unix()) | 69 | rand.Seed(time.Now().Unix()) |
70 | - table.SQLName = fmt.Sprintf("%v_t%v_c%v", limitStringLen(table.SQLName, 40), rand.Intn(1000000), limitStringLen(fmt.Sprintf("%d", ctx.CompanyId), 8)) | 70 | + table.SQLName = fmt.Sprintf("%v_t%v_c%v", limitStringLen(table.SQLName, 30), rand.Intn(1000000), limitStringLen(fmt.Sprintf("%d", ctx.CompanyId), 8)) |
71 | table.Context = ctx | 71 | table.Context = ctx |
72 | return table | 72 | return table |
73 | } | 73 | } |
@@ -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 | } |
@@ -118,6 +118,9 @@ func (d *FilePreviewDto) Load(fileId int, m *domain.DataLoadDataTable, file *red | @@ -118,6 +118,9 @@ func (d *FilePreviewDto) Load(fileId int, m *domain.DataLoadDataTable, file *red | ||
118 | if s == "<NA>" { | 118 | if s == "<NA>" { |
119 | return "" | 119 | return "" |
120 | } | 120 | } |
121 | + if s == "nan" { | ||
122 | + return "" | ||
123 | + } | ||
121 | return s | 124 | return s |
122 | }), true) | 125 | }), true) |
123 | d.Data = domain.GripData(mapData, int64(m.Total)) | 126 | d.Data = domain.GripData(mapData, int64(m.Total)) |
@@ -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}) |
@@ -79,6 +79,13 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int, | @@ -79,6 +79,13 @@ func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int, | ||
79 | continue | 79 | continue |
80 | } | 80 | } |
81 | } | 81 | } |
82 | + if fromField.SQLType != "" && !toField.SqlTypeEqual(fromField) { | ||
83 | + //return nil, fmt.Errorf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name) | ||
84 | + return map[string]interface{}{ | ||
85 | + "result": fmt.Sprintf("字段【%s】的类型与导入数据表的类型不匹配", toField.Name), | ||
86 | + }, nil | ||
87 | + } | ||
88 | + fromField.SQLType = toField.SQLType // 兼容 INT BIGINT | ||
82 | requestData.To = append(requestData.To, toField) | 89 | requestData.To = append(requestData.To, toField) |
83 | requestData.From = append(requestData.From, fromField) | 90 | requestData.From = append(requestData.From, fromField) |
84 | } | 91 | } |
-
请 注册 或 登录 后发表评论