作者 yangfu

feat: query set circle detect

@@ -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 }
  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)