正在显示
14 个修改的文件
包含
1027 行增加
和
95 行删除
@@ -116,10 +116,17 @@ func (querySetService *QuerySetService) DependencyGraph(ctx *domain.Context, dep | @@ -116,10 +116,17 @@ func (querySetService *QuerySetService) DependencyGraph(ctx *domain.Context, dep | ||
116 | defer func() { | 116 | defer func() { |
117 | transactionContext.RollbackTransaction() | 117 | transactionContext.RollbackTransaction() |
118 | }() | 118 | }() |
119 | + | ||
120 | + svr, _ := factory.FastQuerySetServices(transactionContext) | ||
121 | + data, err := svr.DependencyGraph(ctx, dependencyGraphQuery.QuerySetId) | ||
122 | + if err != nil { | ||
123 | + return nil, factory.FastError(err) | ||
124 | + } | ||
125 | + | ||
119 | if err := transactionContext.CommitTransaction(); err != nil { | 126 | if err := transactionContext.CommitTransaction(); err != nil { |
120 | return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error()) | 127 | return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error()) |
121 | } | 128 | } |
122 | - return nil, nil | 129 | + return data, nil |
123 | } | 130 | } |
124 | 131 | ||
125 | // 返回查询集合服务 | 132 | // 返回查询集合服务 |
@@ -27,6 +27,18 @@ type QueryComponent struct { | @@ -27,6 +27,18 @@ type QueryComponent struct { | ||
27 | SelectFromTables []QueryComponentTable `json:"-"` //selectTables | 27 | SelectFromTables []QueryComponentTable `json:"-"` //selectTables |
28 | Description string `json:"description"` | 28 | Description string `json:"description"` |
29 | } | 29 | } |
30 | + | ||
31 | +func (qc QueryComponent) AllSelectExpr() []SelectExpr { | ||
32 | + var res = make([]SelectExpr, 0) | ||
33 | + for _, s := range qc.Selects { | ||
34 | + res = append(res, s.SelectExpr) | ||
35 | + if len(s.SubSelects) > 0 { | ||
36 | + res = append(res, s.SubSelects...) | ||
37 | + } | ||
38 | + } | ||
39 | + return res | ||
40 | +} | ||
41 | + | ||
30 | type ConditionExpr struct { // 条件表达式 | 42 | type ConditionExpr struct { // 条件表达式 |
31 | Id string `json:"id"` | 43 | Id string `json:"id"` |
32 | FieldLeft FieldExpr `json:"fieldLeft"` | 44 | FieldLeft FieldExpr `json:"fieldLeft"` |
@@ -35,6 +47,16 @@ type ConditionExpr struct { // 条件表达式 | @@ -35,6 +47,16 @@ type ConditionExpr struct { // 条件表达式 | ||
35 | AndOr string `json:"andOr"` // and or | 47 | AndOr string `json:"andOr"` // and or |
36 | } | 48 | } |
37 | 49 | ||
50 | +func (c ConditionExpr) Equal(compare ConditionExpr) bool { | ||
51 | + return c.OperatorSymbol == compare.OperatorSymbol && | ||
52 | + c.FieldLeft.ExprSql == compare.FieldLeft.ExprSql && | ||
53 | + c.FieldRight.ExprSql == compare.FieldRight.ExprSql | ||
54 | +} | ||
55 | + | ||
56 | +func (c ConditionExpr) ExprHuman() string { | ||
57 | + return c.FieldLeft.ExprHuman + c.OperatorSymbol + c.FieldRight.ExprHuman | ||
58 | +} | ||
59 | + | ||
38 | type SelectExpr struct { // 查询表达式 | 60 | type SelectExpr struct { // 查询表达式 |
39 | Id string `json:"id"` | 61 | Id string `json:"id"` |
40 | FieldLeft FieldExpr `json:"fieldLeft"` | 62 | FieldLeft FieldExpr `json:"fieldLeft"` |
@@ -42,6 +64,16 @@ type SelectExpr struct { // 查询表达式 | @@ -42,6 +64,16 @@ type SelectExpr struct { // 查询表达式 | ||
42 | Type string `json:"type"` // 1.拆分 2.拆方赋值 3.正常赋值 | 64 | Type string `json:"type"` // 1.拆分 2.拆方赋值 3.正常赋值 |
43 | //SubGroup []SelectExpr `json:"subGroup,omitempty"` | 65 | //SubGroup []SelectExpr `json:"subGroup,omitempty"` |
44 | } | 66 | } |
67 | + | ||
68 | +func (s SelectExpr) Equal(compare SelectExpr) bool { | ||
69 | + return s.FieldLeft.ExprSql == compare.FieldLeft.ExprSql && | ||
70 | + s.FieldRight.ExprSql == compare.FieldRight.ExprSql | ||
71 | +} | ||
72 | + | ||
73 | +func (c SelectExpr) ExprHuman() string { | ||
74 | + return c.FieldLeft.ExprHuman + "=" + c.FieldRight.ExprHuman | ||
75 | +} | ||
76 | + | ||
45 | type SelectExprGroup struct { // 查询表达式 | 77 | type SelectExprGroup struct { // 查询表达式 |
46 | SelectExpr | 78 | SelectExpr |
47 | SubSelects []SelectExpr `json:"subSelects,omitempty"` | 79 | SubSelects []SelectExpr `json:"subSelects,omitempty"` |
@@ -50,8 +82,8 @@ type SelectExprGroup struct { // 查询表达式 | @@ -50,8 +82,8 @@ type SelectExprGroup struct { // 查询表达式 | ||
50 | type FieldExpr struct { | 82 | type FieldExpr struct { |
51 | //LabelColumns []LabelColumn `json:"labelColumns"` | 83 | //LabelColumns []LabelColumn `json:"labelColumns"` |
52 | TableFields []TableField `json:"tableFields"` | 84 | TableFields []TableField `json:"tableFields"` |
53 | - //ExprHuman string `json:"exprHuman"` | ||
54 | - ExprSql string `json:"exprSql"` | 85 | + ExprHuman string `json:"exprHuman"` |
86 | + ExprSql string `json:"exprSql"` | ||
55 | } | 87 | } |
56 | 88 | ||
57 | type LabelColumn struct { | 89 | type LabelColumn struct { |
@@ -101,16 +133,32 @@ func NewQueryComponentTable(t *Table) QueryComponentTable { | @@ -101,16 +133,32 @@ func NewQueryComponentTable(t *Table) QueryComponentTable { | ||
101 | } | 133 | } |
102 | } | 134 | } |
103 | 135 | ||
104 | -func ConditionsToMapById(items []*ConditionExpr) map[string]*ConditionExpr { | ||
105 | - var res = make(map[string]*ConditionExpr) | 136 | +func QueryComponentsToMapById(items []*QueryComponent) map[string]*QueryComponent { |
137 | + var res = make(map[string]*QueryComponent) | ||
138 | + for i := range items { | ||
139 | + res[items[i].Id] = items[i] | ||
140 | + } | ||
141 | + return res | ||
142 | +} | ||
143 | + | ||
144 | +func ConditionsToMapById(items []ConditionExpr) map[string]ConditionExpr { | ||
145 | + var res = make(map[string]ConditionExpr) | ||
146 | + for i := range items { | ||
147 | + res[items[i].Id] = items[i] | ||
148 | + } | ||
149 | + return res | ||
150 | +} | ||
151 | + | ||
152 | +func SelectsToMapById(items []SelectExprGroup) map[string]SelectExprGroup { | ||
153 | + var res = make(map[string]SelectExprGroup) | ||
106 | for i := range items { | 154 | for i := range items { |
107 | res[items[i].Id] = items[i] | 155 | res[items[i].Id] = items[i] |
108 | } | 156 | } |
109 | return res | 157 | return res |
110 | } | 158 | } |
111 | 159 | ||
112 | -func SelectsToMapById(items []*SelectExprGroup) map[string]*SelectExprGroup { | ||
113 | - var res = make(map[string]*SelectExprGroup) | 160 | +func SelectsExprToMapById(items []SelectExpr) map[string]SelectExpr { |
161 | + var res = make(map[string]SelectExpr) | ||
114 | for i := range items { | 162 | for i := range items { |
115 | res[items[i].Id] = items[i] | 163 | res[items[i].Id] = items[i] |
116 | } | 164 | } |
@@ -113,3 +113,10 @@ func (t *Table) MatchField(field *Field) (*Field, bool) { | @@ -113,3 +113,10 @@ func (t *Table) MatchField(field *Field) (*Field, bool) { | ||
113 | } | 113 | } |
114 | return nil, false | 114 | return nil, false |
115 | } | 115 | } |
116 | + | ||
117 | +func (t *Table) DependencyTables() []int { | ||
118 | + if t.TableInfo == nil { | ||
119 | + return []int{} | ||
120 | + } | ||
121 | + return t.TableInfo.DependencyTables | ||
122 | +} |
@@ -3,6 +3,7 @@ package dao | @@ -3,6 +3,7 @@ package dao | ||
3 | import ( | 3 | import ( |
4 | pgTransaction "github.com/linmadan/egglib-go/transaction/pg" | 4 | pgTransaction "github.com/linmadan/egglib-go/transaction/pg" |
5 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | 5 | "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" |
6 | + "time" | ||
6 | ) | 7 | ) |
7 | 8 | ||
8 | func TableDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType domain.TableType) error { | 9 | func TableDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType domain.TableType) error { |
@@ -10,3 +11,9 @@ func TableDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType d | @@ -10,3 +11,9 @@ func TableDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType d | ||
10 | _, err := ptr.PgTx.Exec(sql, tableId, tableType.ToString()) | 11 | _, err := ptr.PgTx.Exec(sql, tableId, tableType.ToString()) |
11 | return err | 12 | return err |
12 | } | 13 | } |
14 | + | ||
15 | +func TableSoftDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType domain.TableType) error { | ||
16 | + sql := "update metadata.tables set deleted_at=? where table_id = ? and table_type = ?" | ||
17 | + _, err := ptr.PgTx.Exec(sql, time.Now(), tableId, tableType.ToString()) | ||
18 | + return err | ||
19 | +} |
@@ -60,6 +60,20 @@ func (ptr *PGLogService) Log(logType domain.LogType, sourceId int, logEntry Log) | @@ -60,6 +60,20 @@ func (ptr *PGLogService) Log(logType domain.LogType, sourceId int, logEntry Log) | ||
60 | return err | 60 | return err |
61 | } | 61 | } |
62 | 62 | ||
63 | +type FastSourceLog struct { | ||
64 | + LogType domain.LogType | ||
65 | + SourceId int | ||
66 | + LogEntry Log | ||
67 | +} | ||
68 | + | ||
69 | +func NewFastSourceLog(logType domain.LogType, sourceId int, logEntry Log) FastSourceLog { | ||
70 | + return FastSourceLog{ | ||
71 | + LogType: logType, | ||
72 | + SourceId: sourceId, | ||
73 | + LogEntry: logEntry, | ||
74 | + } | ||
75 | +} | ||
76 | + | ||
63 | func (ptr *PGLogService) NewLogEntry() domain.LogEntry { | 77 | func (ptr *PGLogService) NewLogEntry() domain.LogEntry { |
64 | return domain.LogEntry{} | 78 | return domain.LogEntry{} |
65 | } | 79 | } |
@@ -396,3 +410,53 @@ func (l *CopyQuerySetLog) Content() string { | @@ -396,3 +410,53 @@ func (l *CopyQuerySetLog) Content() string { | ||
396 | return fmt.Sprintf(`%s"%s"复制为%s"%s""`, domain.EnumsDescription(domain.ObjectTypeMap, l.From.Type), l.From.Name, | 410 | return fmt.Sprintf(`%s"%s"复制为%s"%s""`, domain.EnumsDescription(domain.ObjectTypeMap, l.From.Type), l.From.Name, |
397 | domain.EnumsDescription(domain.ObjectTypeMap, l.To.Type), l.To.Name) | 411 | domain.EnumsDescription(domain.ObjectTypeMap, l.To.Type), l.To.Name) |
398 | } | 412 | } |
413 | + | ||
414 | +type EditQuerySetConditionLog struct { | ||
415 | + domain.LogEntry | ||
416 | + OperationType domain.OperationType | ||
417 | + Sources []string | ||
418 | + SourceTargets [][]string | ||
419 | +} | ||
420 | + | ||
421 | +func (l *EditQuerySetConditionLog) OperateType() string { | ||
422 | + return l.OperationType.ToString() | ||
423 | +} | ||
424 | + | ||
425 | +func (l *EditQuerySetConditionLog) Content() string { | ||
426 | + if l.OperationType == domain.AddSetCondition { | ||
427 | + return fmt.Sprintf("新增条件:%v", strings.Join(l.Sources, ";")) | ||
428 | + } | ||
429 | + if l.OperationType == domain.EditSetCondition { | ||
430 | + items := make([]string, 0) | ||
431 | + for _, sourceTarget := range l.SourceTargets { | ||
432 | + items = append(items, fmt.Sprintf("%v 修改为 %v", sourceTarget[0], sourceTarget[1])) | ||
433 | + } | ||
434 | + return fmt.Sprintf("编辑条件:%v", strings.Join(items, ";")) | ||
435 | + } | ||
436 | + return "删除条件:" + strings.Join(l.Sources, ";") | ||
437 | +} | ||
438 | + | ||
439 | +type EditSelectConditionLog struct { | ||
440 | + domain.LogEntry | ||
441 | + OperationType domain.OperationType // 编辑类型 1:add 2.edit 3.delete | ||
442 | + Sources []string | ||
443 | + SourceTargets [][]string | ||
444 | +} | ||
445 | + | ||
446 | +func (l *EditSelectConditionLog) OperateType() string { | ||
447 | + return l.OperationType.ToString() | ||
448 | +} | ||
449 | + | ||
450 | +func (l *EditSelectConditionLog) Content() string { | ||
451 | + if l.OperationType == domain.AddSelectCondition { | ||
452 | + return fmt.Sprintf("新增拆分规则:%v", strings.Join(l.Sources, ";")) | ||
453 | + } | ||
454 | + if l.OperationType == domain.EditSelectCondition { | ||
455 | + items := make([]string, 0) | ||
456 | + for _, sourceTarget := range l.SourceTargets { | ||
457 | + items = append(items, fmt.Sprintf("%v 修改为 %v", sourceTarget[0], sourceTarget[1])) | ||
458 | + } | ||
459 | + return fmt.Sprintf("编辑拆分规则:%v", strings.Join(items, ";")) | ||
460 | + } | ||
461 | + return "删除拆分规则:" + strings.Join(l.Sources, ";") | ||
462 | +} |
@@ -91,14 +91,16 @@ func (ptr *QuerySetService) Update(ctx *domain.Context, querySetId int, queryCom | @@ -91,14 +91,16 @@ func (ptr *QuerySetService) Update(ctx *domain.Context, querySetId int, queryCom | ||
91 | if err != nil { | 91 | if err != nil { |
92 | return err | 92 | return err |
93 | } | 93 | } |
94 | - // 调用底层的组装sql | ||
95 | 94 | ||
96 | // 生成Table | 95 | // 生成Table |
97 | masterTable := queryComponents[0].MasterTable | 96 | masterTable := queryComponents[0].MasterTable |
98 | - table, err := ptr.CreateOrReFreshQuerySetTable(ctx, qs, masterTable, queryComponents) | 97 | + table, err := ptr.CreateOrUpdateQuerySetTable(ctx, qs, masterTable, queryComponents) |
99 | if err != nil { | 98 | if err != nil { |
100 | return err | 99 | return err |
101 | } | 100 | } |
101 | + | ||
102 | + // 调用底层的组装sql | ||
103 | + | ||
102 | // 生成日志 | 104 | // 生成日志 |
103 | if err = ptr.UpdateQuerySetLog(ctx, qs, queryComponents); err != nil { | 105 | if err = ptr.UpdateQuerySetLog(ctx, qs, queryComponents); err != nil { |
104 | return err | 106 | return err |
@@ -113,14 +115,196 @@ func (ptr *QuerySetService) Update(ctx *domain.Context, querySetId int, queryCom | @@ -113,14 +115,196 @@ func (ptr *QuerySetService) Update(ctx *domain.Context, querySetId int, queryCom | ||
113 | } | 115 | } |
114 | 116 | ||
115 | func (ptr *QuerySetService) UpdateQuerySetLog(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) error { | 117 | func (ptr *QuerySetService) UpdateQuerySetLog(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) error { |
118 | + var res = make([]FastSourceLog, 0) | ||
119 | + if logs := conditionsEditLog(ctx, querySet, queryComponents); len(logs) > 0 { | ||
120 | + res = append(res, logs...) | ||
121 | + } | ||
122 | + if logs := selectsEditLog(ctx, querySet, queryComponents); len(logs) > 0 { | ||
123 | + res = append(res, logs...) | ||
124 | + } | ||
125 | + | ||
126 | + for _, l := range res { | ||
127 | + FastLog(ptr.transactionContext, l.LogType, l.SourceId, l.LogEntry) | ||
128 | + } | ||
116 | return nil | 129 | return nil |
117 | } | 130 | } |
118 | 131 | ||
119 | -func (ptr *QuerySetService) CreateOrReFreshQuerySetTable(ctx *domain.Context, querySet *domain.QuerySet, masterTable domain.QueryComponentTable, queryComponents []*domain.QueryComponent) (*domain.Table, error) { | 132 | +func conditionsEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) []FastSourceLog { |
133 | + var res = make([]FastSourceLog, 0) | ||
134 | + oldQCs := domain.QueryComponentsToMapById(querySet.QueryComponents) | ||
135 | + sourceId := querySet.QuerySetId | ||
136 | + entry := domain.NewLogEntry(querySet.Name, querySet.Type, domain.UnKnown, ctx) | ||
137 | + | ||
138 | + // 新增条件判断 | ||
139 | + addExprList := make([]string, 0) | ||
140 | + editExprList := make([][]string, 0) | ||
141 | + deleteExprList := make([]string, 0) | ||
142 | + | ||
143 | + for _, item := range queryComponents { | ||
144 | + if len(item.Id) == 0 { | ||
145 | + continue | ||
146 | + } | ||
147 | + oldItem, ok := oldQCs[item.Id] | ||
148 | + if !ok { | ||
149 | + continue | ||
150 | + } | ||
151 | + | ||
152 | + // 条件判断编辑情况 | ||
153 | + mapOldItems := domain.ConditionsToMapById(oldItem.Conditions) | ||
154 | + mapNewItems := domain.ConditionsToMapById(item.Conditions) | ||
155 | + for _, add := range item.Conditions { | ||
156 | + if len(add.Id) != 0 { | ||
157 | + continue | ||
158 | + } | ||
159 | + addExprList = append(addExprList, add.ExprHuman()) | ||
160 | + } | ||
161 | + | ||
162 | + // 编辑 | ||
163 | + for _, add := range item.Conditions { | ||
164 | + if len(add.Id) == 0 { | ||
165 | + continue | ||
166 | + } | ||
167 | + v, ok := mapOldItems[add.Id] | ||
168 | + if !ok { | ||
169 | + continue | ||
170 | + } | ||
171 | + if add.Equal(v) { | ||
172 | + continue | ||
173 | + } | ||
174 | + editExprList = append(editExprList, []string{v.ExprHuman(), add.ExprHuman()}) | ||
175 | + } | ||
176 | + | ||
177 | + // 删除 | ||
178 | + for _, item := range oldItem.Conditions { | ||
179 | + if len(item.Id) == 0 { | ||
180 | + continue | ||
181 | + } | ||
182 | + _, ok := mapNewItems[item.Id] | ||
183 | + if ok { | ||
184 | + continue | ||
185 | + } | ||
186 | + deleteExprList = append(deleteExprList, item.ExprHuman()) | ||
187 | + } | ||
188 | + | ||
189 | + } | ||
190 | + | ||
191 | + if len(addExprList) > 0 { | ||
192 | + res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditQuerySetConditionLog{ | ||
193 | + LogEntry: entry, | ||
194 | + OperationType: domain.AddSetCondition, | ||
195 | + Sources: addExprList, | ||
196 | + })) | ||
197 | + } | ||
198 | + | ||
199 | + if len(editExprList) > 0 { | ||
200 | + res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditQuerySetConditionLog{ | ||
201 | + LogEntry: entry, | ||
202 | + OperationType: domain.EditSetCondition, | ||
203 | + SourceTargets: editExprList, | ||
204 | + })) | ||
205 | + } | ||
206 | + | ||
207 | + if len(deleteExprList) > 0 { | ||
208 | + res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditQuerySetConditionLog{ | ||
209 | + LogEntry: entry, | ||
210 | + OperationType: domain.AddSetCondition, | ||
211 | + Sources: deleteExprList, | ||
212 | + })) | ||
213 | + } | ||
214 | + return res | ||
215 | +} | ||
216 | + | ||
217 | +func selectsEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) []FastSourceLog { | ||
218 | + var res = make([]FastSourceLog, 0) | ||
219 | + oldQCs := domain.QueryComponentsToMapById(querySet.QueryComponents) | ||
220 | + //newQCs := domain.QueryComponentsToMapById(queryComponents) | ||
221 | + sourceId := querySet.QuerySetId | ||
222 | + entry := domain.NewLogEntry(querySet.Name, querySet.Type, domain.UnKnown, ctx) | ||
223 | + | ||
224 | + addExprList := make([]string, 0) | ||
225 | + editExprList := make([][]string, 0) | ||
226 | + deleteExprList := make([]string, 0) | ||
227 | + for _, item := range queryComponents { | ||
228 | + if len(item.Id) == 0 { | ||
229 | + continue | ||
230 | + } | ||
231 | + oldItem, ok := oldQCs[item.Id] | ||
232 | + if !ok { | ||
233 | + continue | ||
234 | + } | ||
235 | + | ||
236 | + // 条件判断编辑情况 | ||
237 | + allOldSelectExpr := oldItem.AllSelectExpr() | ||
238 | + mapOldConditions := domain.SelectsExprToMapById(allOldSelectExpr) | ||
239 | + allNewSelectExpr := item.AllSelectExpr() | ||
240 | + mapNewConditions := domain.SelectsExprToMapById(allNewSelectExpr) | ||
241 | + // 新增条件判断 | ||
242 | + for _, item := range allNewSelectExpr { | ||
243 | + if len(item.Id) != 0 { | ||
244 | + continue | ||
245 | + } | ||
246 | + addExprList = append(addExprList, item.ExprHuman()) | ||
247 | + } | ||
248 | + // 编辑 | ||
249 | + for _, item := range allNewSelectExpr { | ||
250 | + if len(item.Id) == 0 { | ||
251 | + continue | ||
252 | + } | ||
253 | + v, ok := mapOldConditions[item.Id] | ||
254 | + if !ok { | ||
255 | + continue | ||
256 | + } | ||
257 | + if item.Equal(v) { | ||
258 | + continue | ||
259 | + } | ||
260 | + editExprList = append(editExprList, []string{v.ExprHuman(), item.ExprHuman()}) | ||
261 | + } | ||
262 | + | ||
263 | + // 删除 | ||
264 | + for _, item := range allOldSelectExpr { | ||
265 | + if len(item.Id) == 0 { | ||
266 | + continue | ||
267 | + } | ||
268 | + _, ok := mapNewConditions[item.Id] | ||
269 | + if ok { | ||
270 | + continue | ||
271 | + } | ||
272 | + deleteExprList = append(deleteExprList, item.ExprHuman()) | ||
273 | + } | ||
274 | + } | ||
275 | + | ||
276 | + if len(addExprList) > 0 { | ||
277 | + res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditSelectConditionLog{ | ||
278 | + LogEntry: entry, | ||
279 | + OperationType: domain.AddSelectCondition, | ||
280 | + Sources: addExprList, | ||
281 | + })) | ||
282 | + } | ||
283 | + | ||
284 | + if len(editExprList) > 0 { | ||
285 | + res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditSelectConditionLog{ | ||
286 | + LogEntry: entry, | ||
287 | + OperationType: domain.EditSelectCondition, | ||
288 | + SourceTargets: editExprList, | ||
289 | + })) | ||
290 | + } | ||
291 | + | ||
292 | + if len(deleteExprList) > 0 { | ||
293 | + res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditSelectConditionLog{ | ||
294 | + LogEntry: entry, | ||
295 | + OperationType: domain.DeleteSelectCondition, | ||
296 | + Sources: deleteExprList, | ||
297 | + })) | ||
298 | + } | ||
299 | + return res | ||
300 | +} | ||
301 | + | ||
302 | +func (ptr *QuerySetService) CreateOrUpdateQuerySetTable(ctx *domain.Context, querySet *domain.QuerySet, masterTable domain.QueryComponentTable, queryComponents []*domain.QueryComponent) (*domain.Table, error) { | ||
120 | var ( | 303 | var ( |
121 | err error | 304 | err error |
122 | foundMasterTable *domain.Table | 305 | foundMasterTable *domain.Table |
123 | ) | 306 | ) |
307 | + dependencyTables := querySet.GetDependencyTables(queryComponents) | ||
124 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) | 308 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) |
125 | foundMasterTable, err = tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": masterTable.TableId}) | 309 | foundMasterTable, err = tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": masterTable.TableId}) |
126 | if err != nil { | 310 | if err != nil { |
@@ -136,8 +320,24 @@ func (ptr *QuerySetService) CreateOrReFreshQuerySetTable(ctx *domain.Context, qu | @@ -136,8 +320,24 @@ func (ptr *QuerySetService) CreateOrReFreshQuerySetTable(ctx *domain.Context, qu | ||
136 | table.DataFields = masterTable.Fields | 320 | table.DataFields = masterTable.Fields |
137 | table.UpdatedAt = time.Now() | 321 | table.UpdatedAt = time.Now() |
138 | } | 322 | } |
323 | + | ||
324 | + // 循环依赖判断 | ||
325 | + tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext) | ||
326 | + if len(dependencyTables) > 0 { | ||
327 | + _, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "tableIds": dependencyTables, "tableTypes": []string{domain.SchemaTable.ToString(), domain.SubProcessTable.ToString()}}) | ||
328 | + if err != nil { | ||
329 | + return nil, err | ||
330 | + } | ||
331 | + if len(tables) > 0 { | ||
332 | + tree := tableDependencyService.TableDependTree(tables, querySet.QuerySetInfo.BindTableId) | ||
333 | + if tableDependencyService.Detect(ctx, tree.EdgesArray()) { | ||
334 | + return nil, NewCircleDependError(tableDependencyService.CircleTable(), querySet) | ||
335 | + } | ||
336 | + } | ||
337 | + } | ||
338 | + | ||
139 | table.TableInfo.ApplyOnModule = domain.ModuleAll | 339 | table.TableInfo.ApplyOnModule = domain.ModuleAll |
140 | - table.TableInfo.DependencyTables = querySet.GetDependencyTables(queryComponents) | 340 | + table.TableInfo.DependencyTables = dependencyTables |
141 | table, err = tableRepository.Save(table) | 341 | table, err = tableRepository.Save(table) |
142 | if err != nil { | 342 | if err != nil { |
143 | return nil, err | 343 | return nil, err |
@@ -257,10 +457,19 @@ func (ptr *QuerySetService) Delete(ctx *domain.Context, querySetId int) error { | @@ -257,10 +457,19 @@ func (ptr *QuerySetService) Delete(ctx *domain.Context, querySetId int) error { | ||
257 | return err | 457 | return err |
258 | } | 458 | } |
259 | querySetRepository, _ := repository.NewQuerySetRepository(ptr.transactionContext) | 459 | querySetRepository, _ := repository.NewQuerySetRepository(ptr.transactionContext) |
460 | + tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext) | ||
260 | for i := range querySets { | 461 | for i := range querySets { |
261 | if _, err := querySetRepository.Remove(querySets[i]); err != nil { | 462 | if _, err := querySetRepository.Remove(querySets[i]); err != nil { |
262 | return err | 463 | return err |
263 | } | 464 | } |
465 | + if querySets[i].QuerySetInfo.BindTableId > 0 { | ||
466 | + if err := tableDependencyService.HasDependencyError(ctx, querySets[i].QuerySetInfo.BindTableId); err != nil { | ||
467 | + return err | ||
468 | + } | ||
469 | + if err := dao.TableSoftDelete(ptr.transactionContext, querySets[i].QuerySetInfo.BindTableId, domain.TableType(querySets[i].Type)); err != nil { | ||
470 | + return err | ||
471 | + } | ||
472 | + } | ||
264 | } | 473 | } |
265 | // 日志 | 474 | // 日志 |
266 | if len(querySets) > 0 { | 475 | if len(querySets) > 0 { |
@@ -353,7 +562,7 @@ func insertQuerySetsByIndex(list []*domain.QuerySet, item *domain.QuerySet, inde | @@ -353,7 +562,7 @@ func insertQuerySetsByIndex(list []*domain.QuerySet, item *domain.QuerySet, inde | ||
353 | func (ptr *QuerySetService) DependencyGraph(ctx *domain.Context, querySetId int) (interface{}, error) { | 562 | func (ptr *QuerySetService) DependencyGraph(ctx *domain.Context, querySetId int) (interface{}, error) { |
354 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) | 563 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) |
355 | _, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "tableTypes": []string{domain.SchemaTable.ToString(), domain.SubProcessTable.ToString()}}) | 564 | _, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "tableTypes": []string{domain.SchemaTable.ToString(), domain.SubProcessTable.ToString()}}) |
356 | - dependencies := make([]dependency, 0) | 565 | + var dependencies TableDependTree = NewTableDependTree(nil, nil) |
357 | if err != nil { | 566 | if err != nil { |
358 | return dependencies, err | 567 | return dependencies, err |
359 | } | 568 | } |
@@ -368,89 +577,10 @@ func (ptr *QuerySetService) DependencyGraph(ctx *domain.Context, querySetId int) | @@ -368,89 +577,10 @@ func (ptr *QuerySetService) DependencyGraph(ctx *domain.Context, querySetId int) | ||
368 | if querySet.QuerySetInfo.BindTableId == 0 { | 577 | if querySet.QuerySetInfo.BindTableId == 0 { |
369 | return dependencies, nil | 578 | return dependencies, nil |
370 | } | 579 | } |
371 | - dependencies = makeDependencyGraph(querySet.QuerySetInfo.BindTableId, tables) | ||
372 | - return dependencies, nil | ||
373 | -} | ||
374 | - | ||
375 | -func makeDependencyGraph(bindTableId int, tables []*domain.Table) []dependency { | ||
376 | - dependencies := make([]dependency, 0) | ||
377 | - tableMap := make(map[int]*domain.Table) | ||
378 | - graph := make(map[int][]int, 0) | ||
379 | - for i := range tables { | ||
380 | - tableMap[tables[i].TableId] = tables[i] | ||
381 | - graph[tables[i].TableId] = tables[i].TableInfo.DependencyTables | ||
382 | - } | ||
383 | - // parent depend | ||
384 | - dependTables := []int{bindTableId} | ||
385 | 580 | ||
386 | - foundDependency := make(map[string]bool, 0) | ||
387 | - for { | ||
388 | - if len(dependTables) == 0 { | ||
389 | - break | ||
390 | - } | ||
391 | - parent := dependTables[0] | ||
392 | - parentTable, ok := tableMap[parent] | ||
393 | - if !ok { | ||
394 | - continue | ||
395 | - } | ||
396 | - for _, dependChildId := range parentTable.TableInfo.DependencyTables { | ||
397 | - dependChild, ok := tableMap[dependChildId] | ||
398 | - if !ok { | ||
399 | - continue | ||
400 | - } | ||
401 | - depend := NewDependency(parentTable.TableId, dependChild) | ||
402 | - if _, ok := foundDependency[depend.String()]; !ok { | ||
403 | - dependencies = append(dependencies, depend) | ||
404 | - } | ||
405 | - } | ||
406 | - dependTables = dependTables[1:] | ||
407 | - } | ||
408 | - | ||
409 | - // dependToChild | ||
410 | - return dependencies | ||
411 | -} | ||
412 | - | ||
413 | -func dependParents(tables []*domain.Table, tableMap map[int]*domain.Table, bindTableId int) []int { | ||
414 | - foundDependTable := make(map[int]bool) | ||
415 | - traceStack := []int{bindTableId} | ||
416 | - res := []int{bindTableId} | ||
417 | - for { | ||
418 | - if len(traceStack) == 0 { | ||
419 | - break | ||
420 | - } | ||
421 | - last := traceStack[0] | ||
422 | - traceStack = traceStack[1:] | ||
423 | - table := tableMap[last] | ||
424 | - if table == nil { | ||
425 | - continue | ||
426 | - } | ||
427 | - // for | ||
428 | - if _, ok := foundDependTable[last]; !ok { | ||
429 | - foundDependTable[last] = true | ||
430 | - } | ||
431 | - } | ||
432 | - return res | ||
433 | -} | ||
434 | - | ||
435 | -func NewDependency(parentId int, child *domain.Table) dependency { | ||
436 | - return dependency{ | ||
437 | - Id: parentId, | ||
438 | - } | ||
439 | -} | ||
440 | - | ||
441 | -type dependency struct { | ||
442 | - // 标识 | ||
443 | - Id int `json:"id"` | ||
444 | - // 表类型 MainTable:主表 SideTable:副表 SubTable:分表 ExcelTable:Excel表 | ||
445 | - Type string `json:"type"` | ||
446 | - // 名称 | ||
447 | - Name string `json:"name"` | ||
448 | - // 依赖的表 | ||
449 | - DependChildId int `json:"dependChildId"` | ||
450 | -} | ||
451 | - | ||
452 | -func (d dependency) String() string { | ||
453 | - return fmt.Sprintf("%d-%d", d.Id, d.DependChildId) | 581 | + tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext) |
582 | + dependencies = tableDependencyService.TableDependTree(tables, querySet.QuerySetInfo.BindTableId) | ||
583 | + return dependencies, nil | ||
454 | } | 584 | } |
455 | 585 | ||
456 | func (ptr *QuerySetService) GetAllChild(ctx *domain.Context, querySetId int, includeSelf bool, onlyNextLevel bool) ([]*domain.QuerySet, error) { | 586 | func (ptr *QuerySetService) GetAllChild(ctx *domain.Context, querySetId int, includeSelf bool, onlyNextLevel bool) ([]*domain.QuerySet, error) { |
1 | +package domainService | ||
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 | +func TestSelectsEditLog(t *testing.T) { | ||
10 | + oldQueryComponents := []*domain.QueryComponent{ | ||
11 | + &domain.QueryComponent{ | ||
12 | + Id: "1", | ||
13 | + Selects: []domain.SelectExprGroup{ | ||
14 | + { | ||
15 | + SelectExpr: domain.SelectExpr{ | ||
16 | + Id: "c_1", | ||
17 | + FieldLeft: domain.FieldExpr{ | ||
18 | + ExprSql: "t.a", | ||
19 | + ExprHuman: "T.a", | ||
20 | + }, | ||
21 | + FieldRight: domain.FieldExpr{ | ||
22 | + ExprSql: "t.b", | ||
23 | + ExprHuman: "T.b", | ||
24 | + }, | ||
25 | + }, | ||
26 | + SubSelects: []domain.SelectExpr{ | ||
27 | + { | ||
28 | + Id: "c_s_1", | ||
29 | + FieldLeft: domain.FieldExpr{ | ||
30 | + ExprSql: "t.a", | ||
31 | + ExprHuman: "T.a", | ||
32 | + }, | ||
33 | + FieldRight: domain.FieldExpr{ | ||
34 | + ExprSql: "t.b", | ||
35 | + ExprHuman: "T.b", | ||
36 | + }, | ||
37 | + }, | ||
38 | + }, | ||
39 | + }, | ||
40 | + { | ||
41 | + SelectExpr: domain.SelectExpr{ | ||
42 | + Id: "c_2", | ||
43 | + FieldLeft: domain.FieldExpr{ | ||
44 | + ExprSql: "t.c", | ||
45 | + ExprHuman: "T.c", | ||
46 | + }, | ||
47 | + FieldRight: domain.FieldExpr{ | ||
48 | + ExprSql: "t.d", | ||
49 | + ExprHuman: "T.d", | ||
50 | + }, | ||
51 | + }, | ||
52 | + }, | ||
53 | + }, | ||
54 | + }, | ||
55 | + } | ||
56 | + newQueryComponents := []*domain.QueryComponent{ | ||
57 | + &domain.QueryComponent{ | ||
58 | + Id: "1", | ||
59 | + Selects: []domain.SelectExprGroup{ | ||
60 | + { | ||
61 | + SelectExpr: domain.SelectExpr{ | ||
62 | + Id: "c_1", | ||
63 | + FieldLeft: domain.FieldExpr{ | ||
64 | + ExprSql: "t.a", | ||
65 | + ExprHuman: "T.a", | ||
66 | + }, | ||
67 | + FieldRight: domain.FieldExpr{ | ||
68 | + ExprSql: "t.c", | ||
69 | + ExprHuman: "T.c", | ||
70 | + }, | ||
71 | + }, | ||
72 | + }, | ||
73 | + }, | ||
74 | + }, | ||
75 | + } | ||
76 | + querySet := &domain.QuerySet{ | ||
77 | + QuerySetId: 1, | ||
78 | + Type: domain.SchemaTable.ToString(), | ||
79 | + Flag: domain.FlagSet, | ||
80 | + Name: "测试集合", | ||
81 | + QueryComponents: oldQueryComponents, | ||
82 | + } | ||
83 | + ctx := &domain.Context{CompanyId: 1, OperatorId: 1, OperatorName: "test"} | ||
84 | + logs := selectsEditLog(ctx, querySet, newQueryComponents) | ||
85 | + assert.Equal(t, len(logs), 3) | ||
86 | +} | ||
87 | + | ||
88 | +func TestConditionsEditLog(t *testing.T) { | ||
89 | + oldQueryComponents := []*domain.QueryComponent{ | ||
90 | + &domain.QueryComponent{ | ||
91 | + Id: "1", | ||
92 | + Conditions: []domain.ConditionExpr{ | ||
93 | + { | ||
94 | + Id: "c_1", | ||
95 | + FieldLeft: domain.FieldExpr{ | ||
96 | + ExprSql: "t.a", | ||
97 | + ExprHuman: "T.a", | ||
98 | + }, | ||
99 | + FieldRight: domain.FieldExpr{ | ||
100 | + ExprSql: "t.b", | ||
101 | + ExprHuman: "T.b", | ||
102 | + }, | ||
103 | + OperatorSymbol: "=", | ||
104 | + }, | ||
105 | + { | ||
106 | + Id: "c_2", | ||
107 | + FieldLeft: domain.FieldExpr{ | ||
108 | + ExprSql: "t.c", | ||
109 | + ExprHuman: "T.c", | ||
110 | + }, | ||
111 | + FieldRight: domain.FieldExpr{ | ||
112 | + ExprSql: "t.d", | ||
113 | + ExprHuman: "T.d", | ||
114 | + }, | ||
115 | + OperatorSymbol: "=", | ||
116 | + }, | ||
117 | + }, | ||
118 | + }, | ||
119 | + } | ||
120 | + newQueryComponents := []*domain.QueryComponent{ | ||
121 | + &domain.QueryComponent{ | ||
122 | + Id: "1", | ||
123 | + Conditions: []domain.ConditionExpr{ | ||
124 | + { | ||
125 | + Id: "c_1", | ||
126 | + FieldLeft: domain.FieldExpr{ | ||
127 | + ExprSql: "t.a", | ||
128 | + ExprHuman: "T.a", | ||
129 | + }, | ||
130 | + FieldRight: domain.FieldExpr{ | ||
131 | + ExprSql: "t.c", | ||
132 | + ExprHuman: "T.c", | ||
133 | + }, | ||
134 | + OperatorSymbol: "=", | ||
135 | + }, | ||
136 | + }, | ||
137 | + }, | ||
138 | + } | ||
139 | + querySet := &domain.QuerySet{ | ||
140 | + QuerySetId: 1, | ||
141 | + Type: domain.SchemaTable.ToString(), | ||
142 | + Flag: domain.FlagSet, | ||
143 | + Name: "测试集合", | ||
144 | + QueryComponents: oldQueryComponents, | ||
145 | + } | ||
146 | + ctx := &domain.Context{CompanyId: 1, OperatorId: 1, OperatorName: "test"} | ||
147 | + logs := conditionsEditLog(ctx, querySet, newQueryComponents) | ||
148 | + assert.Equal(t, len(logs), 2) | ||
149 | +} |
@@ -64,8 +64,12 @@ func (ptr *DeleteDataTableService) DeleteTables(ctx *domain.Context, tables ...* | @@ -64,8 +64,12 @@ func (ptr *DeleteDataTableService) DeleteTables(ctx *domain.Context, tables ...* | ||
64 | return nil | 64 | return nil |
65 | } | 65 | } |
66 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) | 66 | tableRepository, _ := repository.NewTableRepository(ptr.transactionContext) |
67 | + tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext) | ||
67 | var tableIds []int | 68 | var tableIds []int |
68 | for _, t := range tables { | 69 | for _, t := range tables { |
70 | + if err := tableDependencyService.HasDependencyError(ctx, t.TableId); err != nil { | ||
71 | + return err | ||
72 | + } | ||
69 | if _, err := tableRepository.Remove(t); err != nil { | 73 | if _, err := tableRepository.Remove(t); err != nil { |
70 | return err | 74 | return err |
71 | } | 75 | } |
1 | +package domainService | ||
2 | + | ||
3 | +import ( | ||
4 | + "errors" | ||
5 | + "fmt" | ||
6 | + pgTransaction "github.com/linmadan/egglib-go/transaction/pg" | ||
7 | + "github.com/zeromicro/go-zero/core/collection" | ||
8 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
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/utils" | ||
11 | +) | ||
12 | + | ||
13 | +type DependencyError struct { | ||
14 | + t *domain.Table | ||
15 | +} | ||
16 | + | ||
17 | +func (d DependencyError) Error() string { | ||
18 | + return fmt.Sprintf("已关联%v:%v", domain.EnumsDescription(domain.ObjectTypeMap, d.t.TableType), d.t.Name) | ||
19 | +} | ||
20 | + | ||
21 | +type CircleDependError struct { | ||
22 | + circleTable *domain.Table | ||
23 | + querySet *domain.QuerySet | ||
24 | +} | ||
25 | + | ||
26 | +func (d CircleDependError) Error() string { | ||
27 | + circleTableName := "" | ||
28 | + if d.circleTable != nil { | ||
29 | + circleTableName = d.circleTable.Name | ||
30 | + } | ||
31 | + return fmt.Sprintf("循环引用%s(%s)", domain.EnumsDescription(domain.ObjectTypeMap, d.querySet.Type), circleTableName) | ||
32 | +} | ||
33 | + | ||
34 | +func NewCircleDependError(t *domain.Table, qs *domain.QuerySet) CircleDependError { | ||
35 | + return CircleDependError{ | ||
36 | + circleTable: t, | ||
37 | + querySet: qs, | ||
38 | + } | ||
39 | +} | ||
40 | + | ||
41 | +type TableDependencyService struct { | ||
42 | + transactionContext *pgTransaction.TransactionContext | ||
43 | + DetectedCycleCallBack func() | ||
44 | + DebugLog func(string) | ||
45 | + CircleNode int | ||
46 | + TableMap map[int]*domain.Table | ||
47 | +} | ||
48 | + | ||
49 | +func NewTableDependencyService(transactionContext *pgTransaction.TransactionContext) (*TableDependencyService, error) { | ||
50 | + if transactionContext == nil { | ||
51 | + return nil, fmt.Errorf("transactionContext参数不能为nil") | ||
52 | + } else { | ||
53 | + return &TableDependencyService{ | ||
54 | + transactionContext: transactionContext, | ||
55 | + }, nil | ||
56 | + } | ||
57 | +} | ||
58 | + | ||
59 | +func (ptr *TableDependencyService) CircleTable() *domain.Table { | ||
60 | + if ptr.TableMap == nil { | ||
61 | + return nil | ||
62 | + } | ||
63 | + return ptr.TableMap[ptr.CircleNode] | ||
64 | +} | ||
65 | + | ||
66 | +func (ptr *TableDependencyService) HasDependencyError(ctx *domain.Context, dependencyTable int) error { | ||
67 | + table, ok := TableHasDependency(ptr.transactionContext, ctx, dependencyTable) | ||
68 | + if !ok { | ||
69 | + return nil | ||
70 | + } | ||
71 | + return DependencyError{table} | ||
72 | +} | ||
73 | + | ||
74 | +func (ptr *TableDependencyService) Detect(ctx *domain.Context, edges [][]int) bool { | ||
75 | + graph, nodes := ptr.BuildGraph(edges) | ||
76 | + | ||
77 | + visiting := map[int]bool{} | ||
78 | + visited := map[int]bool{} | ||
79 | + | ||
80 | + for _, node := range nodes { | ||
81 | + ptr.log("------------------------------------------\n") | ||
82 | + ptr.log(fmt.Sprintf("dfs node %v \n", node)) | ||
83 | + if ptr.DetectCycle(graph, node, visiting, visited) { | ||
84 | + return true | ||
85 | + } | ||
86 | + } | ||
87 | + return false | ||
88 | +} | ||
89 | + | ||
90 | +func (ptr *TableDependencyService) BuildGraph(edges [][]int) (map[int][]int, []int) { | ||
91 | + var graph = make(map[int][]int) | ||
92 | + var nodes []int | ||
93 | + for _, edge := range edges { | ||
94 | + a, b := edge[0], edge[1] | ||
95 | + if _, ok := graph[a]; !ok { | ||
96 | + nodes = append(nodes, a) | ||
97 | + graph[a] = make([]int, 0) | ||
98 | + } | ||
99 | + graph[a] = append(graph[a], b) | ||
100 | + } | ||
101 | + return graph, nodes | ||
102 | +} | ||
103 | + | ||
104 | +func (ptr *TableDependencyService) DetectCycle(graph map[int][]int, node int, visiting, visited map[int]bool) bool { | ||
105 | + if _, found := visited[node]; found { | ||
106 | + ptr.log(fmt.Sprintf("node %d is already visited(black) -> skip\n", node)) | ||
107 | + return false | ||
108 | + } | ||
109 | + | ||
110 | + if _, found := visiting[node]; found { | ||
111 | + ptr.log(fmt.Sprintf("node %d is in visiting(gray) -> a cycle is detected\n\n", node)) | ||
112 | + ptr.CircleNode = node | ||
113 | + return true | ||
114 | + } | ||
115 | + visiting[node] = true | ||
116 | + ptr.log(fmt.Sprintf("nodes in visiting(gray): %+v\n", visiting)) | ||
117 | + ptr.log(fmt.Sprintf("nodes in visited(black): %+v\n\n", visited)) | ||
118 | + | ||
119 | + for _, descendant := range graph[node] { | ||
120 | + ptr.log(fmt.Sprintf("current node: node %d\n", node)) | ||
121 | + ptr.log(fmt.Sprintf("visit descendant: node %d\n", descendant)) | ||
122 | + if ptr.DetectCycle(graph, descendant, visiting, visited) { | ||
123 | + return true | ||
124 | + } | ||
125 | + } | ||
126 | + | ||
127 | + delete(visiting, node) | ||
128 | + visited[node] = true | ||
129 | + ptr.log(fmt.Sprintf("finish explore node %d\n", node)) | ||
130 | + ptr.log(fmt.Sprintf("nodes in visiting(gray): %+v\n", visiting)) | ||
131 | + ptr.log(fmt.Sprintf("nodes in visited(black): %+v\n\n", visited)) | ||
132 | + return false | ||
133 | +} | ||
134 | + | ||
135 | +func (ptr *TableDependencyService) log(text string) { | ||
136 | + if ptr.DebugLog == nil { | ||
137 | + return | ||
138 | + } | ||
139 | + ptr.DebugLog(text) | ||
140 | +} | ||
141 | + | ||
142 | +func (ptr *TableDependencyService) TableDependTree(tables []*domain.Table, bindTableId int) TableDependTree { | ||
143 | + var ( | ||
144 | + parentMap = ptr.makeParentMap(tables) | ||
145 | + childMap = ptr.makeChildMap(tables) | ||
146 | + rootNodes = ptr.findRootNodes(parentMap, bindTableId) | ||
147 | + tree = ptr.makeTrees(childMap, rootNodes) | ||
148 | + ) | ||
149 | + | ||
150 | + tableMap := make(map[int]*domain.Table) | ||
151 | + for i := range tables { | ||
152 | + tableMap[tables[i].TableId] = tables[i] | ||
153 | + } | ||
154 | + ptr.TableMap = tableMap | ||
155 | + tableDependTree := NewTableDependTree(tree, tableMap) | ||
156 | + tableDependTree.NodeSelected = NewTableNode(tableMap[bindTableId]) | ||
157 | + return tableDependTree | ||
158 | +} | ||
159 | +func (ptr *TableDependencyService) makeParentMap(tables []*domain.Table) map[int][]int { | ||
160 | + res := make(map[int][]int) | ||
161 | + for i := range tables { | ||
162 | + if _, ok := res[tables[i].TableId]; !ok { | ||
163 | + res[tables[i].TableId] = make([]int, 0) | ||
164 | + } | ||
165 | + for _, t := range tables[i].TableInfo.DependencyTables { | ||
166 | + if _, ok := res[t]; !ok { | ||
167 | + res[t] = make([]int, 0) | ||
168 | + } | ||
169 | + res[t] = append(res[t], tables[i].TableId) | ||
170 | + } | ||
171 | + } | ||
172 | + return res | ||
173 | +} | ||
174 | +func (ptr *TableDependencyService) makeChildMap(tables []*domain.Table) map[int][]int { | ||
175 | + res := make(map[int][]int) | ||
176 | + for i := range tables { | ||
177 | + id := tables[i].TableId | ||
178 | + if _, ok := res[id]; !ok { | ||
179 | + res[id] = make([]int, 0) | ||
180 | + } | ||
181 | + | ||
182 | + res[id] = append(res[id], tables[i].TableInfo.DependencyTables...) | ||
183 | + } | ||
184 | + return res | ||
185 | +} | ||
186 | +func (ptr *TableDependencyService) findRootNodes(parentsMap map[int][]int, nodeId int) []int { | ||
187 | + var rootNodes []int | ||
188 | + set := collection.NewSet() | ||
189 | + stack := utils.NewEmptyStack() | ||
190 | + stack.Push(nodeId) | ||
191 | + for { | ||
192 | + if stack.Size() == 0 { | ||
193 | + break | ||
194 | + } | ||
195 | + t := stack.Pop().(int) | ||
196 | + if set.Contains(t) { | ||
197 | + continue | ||
198 | + } | ||
199 | + set.Add(t) | ||
200 | + parents, ok := parentsMap[t] | ||
201 | + if !ok { | ||
202 | + continue | ||
203 | + } | ||
204 | + if len(parents) == 0 { | ||
205 | + rootNodes = append(rootNodes, t) | ||
206 | + } | ||
207 | + for _, item := range parents { | ||
208 | + stack.Push(item) | ||
209 | + } | ||
210 | + } | ||
211 | + return rootNodes | ||
212 | +} | ||
213 | +func (ptr *TableDependencyService) makeTrees(childMap map[int][]int, rootNodes []int) []int { | ||
214 | + set := collection.NewSet() | ||
215 | + stack := utils.NewEmptyStack() | ||
216 | + for _, node := range rootNodes { | ||
217 | + stack.Push(node) | ||
218 | + } | ||
219 | + | ||
220 | + for { | ||
221 | + if stack.Size() == 0 { | ||
222 | + break | ||
223 | + } | ||
224 | + t := stack.Pop().(int) | ||
225 | + if set.Contains(t) { | ||
226 | + continue | ||
227 | + } | ||
228 | + set.Add(t) | ||
229 | + parents, ok := childMap[t] | ||
230 | + if !ok { | ||
231 | + continue | ||
232 | + } | ||
233 | + if len(parents) == 0 { | ||
234 | + rootNodes = append(rootNodes, t) | ||
235 | + } | ||
236 | + for _, item := range parents { | ||
237 | + stack.Push(item) | ||
238 | + } | ||
239 | + } | ||
240 | + return set.KeysInt() | ||
241 | +} | ||
242 | + | ||
243 | +func TableHasDependency(transactionContext *pgTransaction.TransactionContext, ctx *domain.Context, dependencyTable int) (*domain.Table, bool) { | ||
244 | + tableRepository, _ := repository.NewTableRepository(transactionContext) | ||
245 | + table, err := tableRepository.FindOne(map[string]interface{}{"context": ctx, "dependencyTable": dependencyTable}) | ||
246 | + if errors.Is(err, domain.ErrorNotFound) { | ||
247 | + return nil, false | ||
248 | + } | ||
249 | + if table == nil && err != nil { | ||
250 | + return nil, false | ||
251 | + } | ||
252 | + return table, true | ||
253 | +} | ||
254 | + | ||
255 | +type TableDependTree struct { | ||
256 | + Tree []int `json:"-"` | ||
257 | + Nodes []TableNode `json:"nodes"` | ||
258 | + Edges []TableEdge `json:"edges"` | ||
259 | + NodeSelected TableNode `json:"nodeSelected"` | ||
260 | +} | ||
261 | + | ||
262 | +func (td TableDependTree) EdgesArray() [][]int { | ||
263 | + var res = make([][]int, 0) | ||
264 | + for _, edge := range td.Edges { | ||
265 | + res = append(res, []int{edge.Id, edge.DependChildId}) | ||
266 | + } | ||
267 | + return res | ||
268 | +} | ||
269 | + | ||
270 | +type TableNode struct { | ||
271 | + TableId int `json:"tableId"` | ||
272 | + // 表类型 MainTable:主表 SideTable:副表 SubTable:分表 | ||
273 | + Type string `json:"type"` | ||
274 | + // 名称 | ||
275 | + Name string `json:"name"` | ||
276 | + // 依赖关联的表 | ||
277 | + DependencyTables []int `json:"-"` | ||
278 | +} | ||
279 | +type TableEdge struct { | ||
280 | + // 标识 | ||
281 | + Id int `json:"tableId"` | ||
282 | + // 表类型 MainTable:主表 SideTable:副表 SubTable:分表 | ||
283 | + Type string `json:"type"` | ||
284 | + // 名称 | ||
285 | + Name string `json:"name"` | ||
286 | + // 依赖的表 | ||
287 | + DependChildId int `json:"dependTableId"` | ||
288 | +} | ||
289 | + | ||
290 | +func NewTableDependTree(tree []int, tableMap map[int]*domain.Table) TableDependTree { | ||
291 | + dependTree := TableDependTree{ | ||
292 | + Nodes: make([]TableNode, 0), | ||
293 | + Edges: make([]TableEdge, 0), | ||
294 | + Tree: tree, | ||
295 | + } | ||
296 | + | ||
297 | + for _, node := range tree { | ||
298 | + t, ok := tableMap[node] | ||
299 | + if !ok { | ||
300 | + continue | ||
301 | + } | ||
302 | + dependTree.Nodes = append(dependTree.Nodes, NewTableNode(t)) | ||
303 | + for _, item := range t.DependencyTables() { | ||
304 | + dependTable, ok := tableMap[item] | ||
305 | + if !ok { | ||
306 | + continue | ||
307 | + } | ||
308 | + dependTree.Edges = append(dependTree.Edges, NewTableEdge(t, dependTable)) | ||
309 | + } | ||
310 | + } | ||
311 | + return dependTree | ||
312 | +} | ||
313 | +func NewTableNode(table *domain.Table) TableNode { | ||
314 | + return TableNode{ | ||
315 | + TableId: table.TableId, | ||
316 | + Type: table.TableType, | ||
317 | + Name: table.Name, | ||
318 | + DependencyTables: table.TableInfo.DependencyTables, | ||
319 | + } | ||
320 | +} | ||
321 | +func NewTableEdge(table *domain.Table, dependTable *domain.Table) TableEdge { | ||
322 | + return TableEdge{ | ||
323 | + Id: table.TableId, | ||
324 | + Type: table.TableType, | ||
325 | + Name: table.Name, | ||
326 | + DependChildId: dependTable.TableId, | ||
327 | + } | ||
328 | +} |
1 | +package domainService | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "github.com/stretchr/testify/assert" | ||
6 | + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" | ||
7 | + "sort" | ||
8 | + "testing" | ||
9 | +) | ||
10 | + | ||
11 | +func TestTableDependTree(t *testing.T) { | ||
12 | + /* | ||
13 | + 5 8 | ||
14 | + / \ / \ | ||
15 | + 1 3 9 10 | ||
16 | + / / \ / \ | ||
17 | + 4 6 2 11 12 | ||
18 | + \ | ||
19 | + 7 | ||
20 | + */ | ||
21 | + tables := []*domain.Table{ | ||
22 | + newTable(5, 1, 3), | ||
23 | + newTable(1, 4), | ||
24 | + newTable(4), | ||
25 | + newTable(3, 6, 2), | ||
26 | + newTable(6), | ||
27 | + newTable(2, 7), | ||
28 | + newTable(7), | ||
29 | + | ||
30 | + newTable(8, 9, 10), | ||
31 | + newTable(10), | ||
32 | + newTable(9, 11, 12), | ||
33 | + newTable(11), | ||
34 | + newTable(12, 9), | ||
35 | + } | ||
36 | + tableDependencyService := &TableDependencyService{DebugLog: func(s string) { | ||
37 | + //t.Log(s) | ||
38 | + }} | ||
39 | + | ||
40 | + inputs := []struct { | ||
41 | + BindTableId int | ||
42 | + Trees []int | ||
43 | + Circle bool | ||
44 | + }{ | ||
45 | + { | ||
46 | + 3, | ||
47 | + []int{1, 2, 3, 4, 5, 6, 7}, | ||
48 | + false, | ||
49 | + }, | ||
50 | + { | ||
51 | + 4, | ||
52 | + []int{1, 2, 3, 4, 5, 6, 7}, | ||
53 | + false, | ||
54 | + }, | ||
55 | + | ||
56 | + { | ||
57 | + 12, | ||
58 | + []int{8, 9, 10, 11, 12}, | ||
59 | + true, | ||
60 | + }, | ||
61 | + { | ||
62 | + 8, | ||
63 | + []int{8, 9, 10, 11, 12}, | ||
64 | + true, | ||
65 | + }, | ||
66 | + { | ||
67 | + 10, | ||
68 | + []int{8, 9, 10, 11, 12}, | ||
69 | + true, | ||
70 | + }, | ||
71 | + } | ||
72 | + for _, input := range inputs { | ||
73 | + tree := tableDependencyService.TableDependTree(tables, input.BindTableId) | ||
74 | + got := tree.Tree | ||
75 | + sort.Ints(got) | ||
76 | + assert.Equal(t, got, input.Trees) | ||
77 | + | ||
78 | + assert.Equal(t, tableDependencyService.Detect(nil, tree.EdgesArray()), input.Circle) | ||
79 | + } | ||
80 | +} | ||
81 | + | ||
82 | +func newTable(id int, dependTables ...int) *domain.Table { | ||
83 | + return &domain.Table{ | ||
84 | + TableId: id, | ||
85 | + Name: fmt.Sprintf("t%d", id), | ||
86 | + TableInfo: &domain.TableInfo{ | ||
87 | + DependencyTables: dependTables, | ||
88 | + }, | ||
89 | + } | ||
90 | +} |
@@ -55,6 +55,13 @@ func (ptr *UpdateTableStructService) UpdateTableStruct(ctx *domain.Context, tabl | @@ -55,6 +55,13 @@ func (ptr *UpdateTableStructService) UpdateTableStruct(ctx *domain.Context, tabl | ||
55 | if _, err = tableRepository.Save(mainTable); err != nil { | 55 | if _, err = tableRepository.Save(mainTable); err != nil { |
56 | return nil, err | 56 | return nil, err |
57 | } | 57 | } |
58 | + // 字段被删除 | ||
59 | + if len(deletes) > 0 { | ||
60 | + tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext) | ||
61 | + if err := tableDependencyService.HasDependencyError(ctx, tableId); err != nil { | ||
62 | + return nil, err | ||
63 | + } | ||
64 | + } | ||
58 | 65 | ||
59 | // Log | 66 | // Log |
60 | // 日志 | 67 | // 日志 |
@@ -144,6 +144,9 @@ func (repository *TableRepository) FindOne(queryOptions map[string]interface{}) | @@ -144,6 +144,9 @@ func (repository *TableRepository) FindOne(queryOptions map[string]interface{}) | ||
144 | if v, ok := queryOptions["tableTypes"]; ok && len(v.([]string)) > 0 { | 144 | if v, ok := queryOptions["tableTypes"]; ok && len(v.([]string)) > 0 { |
145 | query.Where(`table_type in (?)`, pg.In(v.([]string))) | 145 | query.Where(`table_type in (?)`, pg.In(v.([]string))) |
146 | } | 146 | } |
147 | + if v, ok := queryOptions["dependencyTable"]; ok && v.(int) > 0 { | ||
148 | + query.Where(`table_info->'dependencyTables' @> '[?]'`, v.(int)) | ||
149 | + } | ||
147 | if err := query.First(); err != nil { | 150 | if err := query.First(); err != nil { |
148 | if err.Error() == "pg: no rows in result set" { | 151 | if err.Error() == "pg: no rows in result set" { |
149 | return nil, domain.ErrorNotFound | 152 | return nil, domain.ErrorNotFound |
@@ -166,6 +169,9 @@ func (repository *TableRepository) Find(queryOptions map[string]interface{}) (in | @@ -166,6 +169,9 @@ func (repository *TableRepository) Find(queryOptions map[string]interface{}) (in | ||
166 | query.SetWhereByQueryOption(fmt.Sprintf("name like '%%%v%%'", queryOptions["name"]), "name") | 169 | query.SetWhereByQueryOption(fmt.Sprintf("name like '%%%v%%'", queryOptions["name"]), "name") |
167 | 170 | ||
168 | query.SetWhereByQueryOption("parent_id = ?", "parentId") | 171 | query.SetWhereByQueryOption("parent_id = ?", "parentId") |
172 | + if v, ok := queryOptions["tableIds"]; ok && len(v.([]int)) > 0 { | ||
173 | + query.Where(`table_id in (?)`, pg.In(v.([]int))) | ||
174 | + } | ||
169 | if v, ok := queryOptions["tableTypes"]; ok && len(v.([]string)) > 0 { | 175 | if v, ok := queryOptions["tableTypes"]; ok && len(v.([]string)) > 0 { |
170 | query.Where(`table_type in (?)`, pg.In(v.([]string))) | 176 | query.Where(`table_type in (?)`, pg.In(v.([]string))) |
171 | } | 177 | } |
pkg/infrastructure/utils/stack.go
0 → 100644
1 | +package utils | ||
2 | + | ||
3 | +import "sync" | ||
4 | + | ||
5 | +// Item interface to store any data type in stack | ||
6 | +type Item interface{} | ||
7 | + | ||
8 | +// Stack struct which contains a list of Items | ||
9 | +type Stack struct { | ||
10 | + items []Item | ||
11 | + mutex sync.Mutex | ||
12 | +} | ||
13 | + | ||
14 | +// NewEmptyStack() returns a new instance of Stack with zero elements | ||
15 | +func NewEmptyStack() *Stack { | ||
16 | + return &Stack{ | ||
17 | + items: nil, | ||
18 | + } | ||
19 | +} | ||
20 | + | ||
21 | +// NewStack() returns a new instance of Stack with list of specified elements | ||
22 | +func NewStack(items []Item) *Stack { | ||
23 | + return &Stack{ | ||
24 | + items: items, | ||
25 | + } | ||
26 | +} | ||
27 | + | ||
28 | +// Push() adds new item to top of existing/empty stack | ||
29 | +func (stack *Stack) Push(item Item) { | ||
30 | + stack.mutex.Lock() | ||
31 | + defer stack.mutex.Unlock() | ||
32 | + | ||
33 | + stack.items = append(stack.items, item) | ||
34 | +} | ||
35 | + | ||
36 | +// Pop() removes most recent item(top) from stack | ||
37 | +func (stack *Stack) Pop() Item { | ||
38 | + stack.mutex.Lock() | ||
39 | + defer stack.mutex.Unlock() | ||
40 | + | ||
41 | + if len(stack.items) == 0 { | ||
42 | + return nil | ||
43 | + } | ||
44 | + | ||
45 | + lastItem := stack.items[len(stack.items)-1] | ||
46 | + stack.items = stack.items[:len(stack.items)-1] | ||
47 | + | ||
48 | + return lastItem | ||
49 | +} | ||
50 | + | ||
51 | +// IsEmpty() returns whether the stack is empty or not (boolean result) | ||
52 | +func (stack *Stack) IsEmpty() bool { | ||
53 | + stack.mutex.Lock() | ||
54 | + defer stack.mutex.Unlock() | ||
55 | + | ||
56 | + return len(stack.items) == 0 | ||
57 | +} | ||
58 | + | ||
59 | +// Top() returns the last inserted item in stack without removing it. | ||
60 | +func (stack *Stack) Top() Item { | ||
61 | + stack.mutex.Lock() | ||
62 | + defer stack.mutex.Unlock() | ||
63 | + | ||
64 | + if len(stack.items) == 0 { | ||
65 | + return nil | ||
66 | + } | ||
67 | + | ||
68 | + return stack.items[len(stack.items)-1] | ||
69 | +} | ||
70 | + | ||
71 | +// Size() returns the number of items currently in the stack | ||
72 | +func (stack *Stack) Size() int { | ||
73 | + stack.mutex.Lock() | ||
74 | + defer stack.mutex.Unlock() | ||
75 | + | ||
76 | + return len(stack.items) | ||
77 | +} | ||
78 | + | ||
79 | +// Clear() removes all items from the stack | ||
80 | +func (stack *Stack) Clear() { | ||
81 | + stack.mutex.Lock() | ||
82 | + defer stack.mutex.Unlock() | ||
83 | + | ||
84 | + stack.items = nil | ||
85 | +} |
@@ -22,7 +22,7 @@ func (controller *QuerySetController) CreateQuerySet() { | @@ -22,7 +22,7 @@ func (controller *QuerySetController) CreateQuerySet() { | ||
22 | func (controller *QuerySetController) UpdateQuerySet() { | 22 | func (controller *QuerySetController) UpdateQuerySet() { |
23 | querySetService := service.NewQuerySetService(nil) | 23 | querySetService := service.NewQuerySetService(nil) |
24 | updateQuerySetCommand := &command.UpdateQuerySetCommand{} | 24 | updateQuerySetCommand := &command.UpdateQuerySetCommand{} |
25 | - controller.Unmarshal(updateQuerySetCommand) | 25 | + Must(controller.Unmarshal(updateQuerySetCommand)) |
26 | Id, _ := controller.GetInt(":querySetId") | 26 | Id, _ := controller.GetInt(":querySetId") |
27 | updateQuerySetCommand.QuerySetId = Id | 27 | updateQuerySetCommand.QuerySetId = Id |
28 | data, err := querySetService.UpdateQuerySet(ParseContext(controller.BaseController), updateQuerySetCommand) | 28 | data, err := querySetService.UpdateQuerySet(ParseContext(controller.BaseController), updateQuerySetCommand) |
-
请 注册 或 登录 后发表评论