作者 yangfu

feat: query set circle detect

... ... @@ -116,10 +116,17 @@ func (querySetService *QuerySetService) DependencyGraph(ctx *domain.Context, dep
defer func() {
transactionContext.RollbackTransaction()
}()
svr, _ := factory.FastQuerySetServices(transactionContext)
data, err := svr.DependencyGraph(ctx, dependencyGraphQuery.QuerySetId)
if err != nil {
return nil, factory.FastError(err)
}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return nil, nil
return data, nil
}
// 返回查询集合服务
... ...
... ... @@ -27,6 +27,18 @@ type QueryComponent struct {
SelectFromTables []QueryComponentTable `json:"-"` //selectTables
Description string `json:"description"`
}
func (qc QueryComponent) AllSelectExpr() []SelectExpr {
var res = make([]SelectExpr, 0)
for _, s := range qc.Selects {
res = append(res, s.SelectExpr)
if len(s.SubSelects) > 0 {
res = append(res, s.SubSelects...)
}
}
return res
}
type ConditionExpr struct { // 条件表达式
Id string `json:"id"`
FieldLeft FieldExpr `json:"fieldLeft"`
... ... @@ -35,6 +47,16 @@ type ConditionExpr struct { // 条件表达式
AndOr string `json:"andOr"` // and or
}
func (c ConditionExpr) Equal(compare ConditionExpr) bool {
return c.OperatorSymbol == compare.OperatorSymbol &&
c.FieldLeft.ExprSql == compare.FieldLeft.ExprSql &&
c.FieldRight.ExprSql == compare.FieldRight.ExprSql
}
func (c ConditionExpr) ExprHuman() string {
return c.FieldLeft.ExprHuman + c.OperatorSymbol + c.FieldRight.ExprHuman
}
type SelectExpr struct { // 查询表达式
Id string `json:"id"`
FieldLeft FieldExpr `json:"fieldLeft"`
... ... @@ -42,6 +64,16 @@ type SelectExpr struct { // 查询表达式
Type string `json:"type"` // 1.拆分 2.拆方赋值 3.正常赋值
//SubGroup []SelectExpr `json:"subGroup,omitempty"`
}
func (s SelectExpr) Equal(compare SelectExpr) bool {
return s.FieldLeft.ExprSql == compare.FieldLeft.ExprSql &&
s.FieldRight.ExprSql == compare.FieldRight.ExprSql
}
func (c SelectExpr) ExprHuman() string {
return c.FieldLeft.ExprHuman + "=" + c.FieldRight.ExprHuman
}
type SelectExprGroup struct { // 查询表达式
SelectExpr
SubSelects []SelectExpr `json:"subSelects,omitempty"`
... ... @@ -50,8 +82,8 @@ type SelectExprGroup struct { // 查询表达式
type FieldExpr struct {
//LabelColumns []LabelColumn `json:"labelColumns"`
TableFields []TableField `json:"tableFields"`
//ExprHuman string `json:"exprHuman"`
ExprSql string `json:"exprSql"`
ExprHuman string `json:"exprHuman"`
ExprSql string `json:"exprSql"`
}
type LabelColumn struct {
... ... @@ -101,16 +133,32 @@ func NewQueryComponentTable(t *Table) QueryComponentTable {
}
}
func ConditionsToMapById(items []*ConditionExpr) map[string]*ConditionExpr {
var res = make(map[string]*ConditionExpr)
func QueryComponentsToMapById(items []*QueryComponent) map[string]*QueryComponent {
var res = make(map[string]*QueryComponent)
for i := range items {
res[items[i].Id] = items[i]
}
return res
}
func ConditionsToMapById(items []ConditionExpr) map[string]ConditionExpr {
var res = make(map[string]ConditionExpr)
for i := range items {
res[items[i].Id] = items[i]
}
return res
}
func SelectsToMapById(items []SelectExprGroup) map[string]SelectExprGroup {
var res = make(map[string]SelectExprGroup)
for i := range items {
res[items[i].Id] = items[i]
}
return res
}
func SelectsToMapById(items []*SelectExprGroup) map[string]*SelectExprGroup {
var res = make(map[string]*SelectExprGroup)
func SelectsExprToMapById(items []SelectExpr) map[string]SelectExpr {
var res = make(map[string]SelectExpr)
for i := range items {
res[items[i].Id] = items[i]
}
... ...
... ... @@ -113,3 +113,10 @@ func (t *Table) MatchField(field *Field) (*Field, bool) {
}
return nil, false
}
func (t *Table) DependencyTables() []int {
if t.TableInfo == nil {
return []int{}
}
return t.TableInfo.DependencyTables
}
... ...
... ... @@ -3,6 +3,7 @@ package dao
import (
pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"time"
)
func TableDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType domain.TableType) error {
... ... @@ -10,3 +11,9 @@ func TableDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType d
_, err := ptr.PgTx.Exec(sql, tableId, tableType.ToString())
return err
}
func TableSoftDelete(ptr *pgTransaction.TransactionContext, tableId int, tableType domain.TableType) error {
sql := "update metadata.tables set deleted_at=? where table_id = ? and table_type = ?"
_, err := ptr.PgTx.Exec(sql, time.Now(), tableId, tableType.ToString())
return err
}
... ...
... ... @@ -60,6 +60,20 @@ func (ptr *PGLogService) Log(logType domain.LogType, sourceId int, logEntry Log)
return err
}
type FastSourceLog struct {
LogType domain.LogType
SourceId int
LogEntry Log
}
func NewFastSourceLog(logType domain.LogType, sourceId int, logEntry Log) FastSourceLog {
return FastSourceLog{
LogType: logType,
SourceId: sourceId,
LogEntry: logEntry,
}
}
func (ptr *PGLogService) NewLogEntry() domain.LogEntry {
return domain.LogEntry{}
}
... ... @@ -396,3 +410,53 @@ func (l *CopyQuerySetLog) Content() string {
return fmt.Sprintf(`%s"%s"复制为%s"%s""`, domain.EnumsDescription(domain.ObjectTypeMap, l.From.Type), l.From.Name,
domain.EnumsDescription(domain.ObjectTypeMap, l.To.Type), l.To.Name)
}
type EditQuerySetConditionLog struct {
domain.LogEntry
OperationType domain.OperationType
Sources []string
SourceTargets [][]string
}
func (l *EditQuerySetConditionLog) OperateType() string {
return l.OperationType.ToString()
}
func (l *EditQuerySetConditionLog) Content() string {
if l.OperationType == domain.AddSetCondition {
return fmt.Sprintf("新增条件:%v", strings.Join(l.Sources, ";"))
}
if l.OperationType == domain.EditSetCondition {
items := make([]string, 0)
for _, sourceTarget := range l.SourceTargets {
items = append(items, fmt.Sprintf("%v 修改为 %v", sourceTarget[0], sourceTarget[1]))
}
return fmt.Sprintf("编辑条件:%v", strings.Join(items, ";"))
}
return "删除条件:" + strings.Join(l.Sources, ";")
}
type EditSelectConditionLog struct {
domain.LogEntry
OperationType domain.OperationType // 编辑类型 1:add 2.edit 3.delete
Sources []string
SourceTargets [][]string
}
func (l *EditSelectConditionLog) OperateType() string {
return l.OperationType.ToString()
}
func (l *EditSelectConditionLog) Content() string {
if l.OperationType == domain.AddSelectCondition {
return fmt.Sprintf("新增拆分规则:%v", strings.Join(l.Sources, ";"))
}
if l.OperationType == domain.EditSelectCondition {
items := make([]string, 0)
for _, sourceTarget := range l.SourceTargets {
items = append(items, fmt.Sprintf("%v 修改为 %v", sourceTarget[0], sourceTarget[1]))
}
return fmt.Sprintf("编辑拆分规则:%v", strings.Join(items, ";"))
}
return "删除拆分规则:" + strings.Join(l.Sources, ";")
}
... ...
... ... @@ -91,14 +91,16 @@ func (ptr *QuerySetService) Update(ctx *domain.Context, querySetId int, queryCom
if err != nil {
return err
}
// 调用底层的组装sql
// 生成Table
masterTable := queryComponents[0].MasterTable
table, err := ptr.CreateOrReFreshQuerySetTable(ctx, qs, masterTable, queryComponents)
table, err := ptr.CreateOrUpdateQuerySetTable(ctx, qs, masterTable, queryComponents)
if err != nil {
return err
}
// 调用底层的组装sql
// 生成日志
if err = ptr.UpdateQuerySetLog(ctx, qs, queryComponents); err != nil {
return err
... ... @@ -113,14 +115,196 @@ func (ptr *QuerySetService) Update(ctx *domain.Context, querySetId int, queryCom
}
func (ptr *QuerySetService) UpdateQuerySetLog(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) error {
var res = make([]FastSourceLog, 0)
if logs := conditionsEditLog(ctx, querySet, queryComponents); len(logs) > 0 {
res = append(res, logs...)
}
if logs := selectsEditLog(ctx, querySet, queryComponents); len(logs) > 0 {
res = append(res, logs...)
}
for _, l := range res {
FastLog(ptr.transactionContext, l.LogType, l.SourceId, l.LogEntry)
}
return nil
}
func (ptr *QuerySetService) CreateOrReFreshQuerySetTable(ctx *domain.Context, querySet *domain.QuerySet, masterTable domain.QueryComponentTable, queryComponents []*domain.QueryComponent) (*domain.Table, error) {
func conditionsEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) []FastSourceLog {
var res = make([]FastSourceLog, 0)
oldQCs := domain.QueryComponentsToMapById(querySet.QueryComponents)
sourceId := querySet.QuerySetId
entry := domain.NewLogEntry(querySet.Name, querySet.Type, domain.UnKnown, ctx)
// 新增条件判断
addExprList := make([]string, 0)
editExprList := make([][]string, 0)
deleteExprList := make([]string, 0)
for _, item := range queryComponents {
if len(item.Id) == 0 {
continue
}
oldItem, ok := oldQCs[item.Id]
if !ok {
continue
}
// 条件判断编辑情况
mapOldItems := domain.ConditionsToMapById(oldItem.Conditions)
mapNewItems := domain.ConditionsToMapById(item.Conditions)
for _, add := range item.Conditions {
if len(add.Id) != 0 {
continue
}
addExprList = append(addExprList, add.ExprHuman())
}
// 编辑
for _, add := range item.Conditions {
if len(add.Id) == 0 {
continue
}
v, ok := mapOldItems[add.Id]
if !ok {
continue
}
if add.Equal(v) {
continue
}
editExprList = append(editExprList, []string{v.ExprHuman(), add.ExprHuman()})
}
// 删除
for _, item := range oldItem.Conditions {
if len(item.Id) == 0 {
continue
}
_, ok := mapNewItems[item.Id]
if ok {
continue
}
deleteExprList = append(deleteExprList, item.ExprHuman())
}
}
if len(addExprList) > 0 {
res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditQuerySetConditionLog{
LogEntry: entry,
OperationType: domain.AddSetCondition,
Sources: addExprList,
}))
}
if len(editExprList) > 0 {
res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditQuerySetConditionLog{
LogEntry: entry,
OperationType: domain.EditSetCondition,
SourceTargets: editExprList,
}))
}
if len(deleteExprList) > 0 {
res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditQuerySetConditionLog{
LogEntry: entry,
OperationType: domain.AddSetCondition,
Sources: deleteExprList,
}))
}
return res
}
func selectsEditLog(ctx *domain.Context, querySet *domain.QuerySet, queryComponents []*domain.QueryComponent) []FastSourceLog {
var res = make([]FastSourceLog, 0)
oldQCs := domain.QueryComponentsToMapById(querySet.QueryComponents)
//newQCs := domain.QueryComponentsToMapById(queryComponents)
sourceId := querySet.QuerySetId
entry := domain.NewLogEntry(querySet.Name, querySet.Type, domain.UnKnown, ctx)
addExprList := make([]string, 0)
editExprList := make([][]string, 0)
deleteExprList := make([]string, 0)
for _, item := range queryComponents {
if len(item.Id) == 0 {
continue
}
oldItem, ok := oldQCs[item.Id]
if !ok {
continue
}
// 条件判断编辑情况
allOldSelectExpr := oldItem.AllSelectExpr()
mapOldConditions := domain.SelectsExprToMapById(allOldSelectExpr)
allNewSelectExpr := item.AllSelectExpr()
mapNewConditions := domain.SelectsExprToMapById(allNewSelectExpr)
// 新增条件判断
for _, item := range allNewSelectExpr {
if len(item.Id) != 0 {
continue
}
addExprList = append(addExprList, item.ExprHuman())
}
// 编辑
for _, item := range allNewSelectExpr {
if len(item.Id) == 0 {
continue
}
v, ok := mapOldConditions[item.Id]
if !ok {
continue
}
if item.Equal(v) {
continue
}
editExprList = append(editExprList, []string{v.ExprHuman(), item.ExprHuman()})
}
// 删除
for _, item := range allOldSelectExpr {
if len(item.Id) == 0 {
continue
}
_, ok := mapNewConditions[item.Id]
if ok {
continue
}
deleteExprList = append(deleteExprList, item.ExprHuman())
}
}
if len(addExprList) > 0 {
res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditSelectConditionLog{
LogEntry: entry,
OperationType: domain.AddSelectCondition,
Sources: addExprList,
}))
}
if len(editExprList) > 0 {
res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditSelectConditionLog{
LogEntry: entry,
OperationType: domain.EditSelectCondition,
SourceTargets: editExprList,
}))
}
if len(deleteExprList) > 0 {
res = append(res, NewFastSourceLog(domain.QuerySetLog, sourceId, &EditSelectConditionLog{
LogEntry: entry,
OperationType: domain.DeleteSelectCondition,
Sources: deleteExprList,
}))
}
return res
}
func (ptr *QuerySetService) CreateOrUpdateQuerySetTable(ctx *domain.Context, querySet *domain.QuerySet, masterTable domain.QueryComponentTable, queryComponents []*domain.QueryComponent) (*domain.Table, error) {
var (
err error
foundMasterTable *domain.Table
)
dependencyTables := querySet.GetDependencyTables(queryComponents)
tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
foundMasterTable, err = tableRepository.FindOne(map[string]interface{}{"context": ctx, "tableId": masterTable.TableId})
if err != nil {
... ... @@ -136,8 +320,24 @@ func (ptr *QuerySetService) CreateOrReFreshQuerySetTable(ctx *domain.Context, qu
table.DataFields = masterTable.Fields
table.UpdatedAt = time.Now()
}
// 循环依赖判断
tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext)
if len(dependencyTables) > 0 {
_, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "tableIds": dependencyTables, "tableTypes": []string{domain.SchemaTable.ToString(), domain.SubProcessTable.ToString()}})
if err != nil {
return nil, err
}
if len(tables) > 0 {
tree := tableDependencyService.TableDependTree(tables, querySet.QuerySetInfo.BindTableId)
if tableDependencyService.Detect(ctx, tree.EdgesArray()) {
return nil, NewCircleDependError(tableDependencyService.CircleTable(), querySet)
}
}
}
table.TableInfo.ApplyOnModule = domain.ModuleAll
table.TableInfo.DependencyTables = querySet.GetDependencyTables(queryComponents)
table.TableInfo.DependencyTables = dependencyTables
table, err = tableRepository.Save(table)
if err != nil {
return nil, err
... ... @@ -257,10 +457,19 @@ func (ptr *QuerySetService) Delete(ctx *domain.Context, querySetId int) error {
return err
}
querySetRepository, _ := repository.NewQuerySetRepository(ptr.transactionContext)
tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext)
for i := range querySets {
if _, err := querySetRepository.Remove(querySets[i]); err != nil {
return err
}
if querySets[i].QuerySetInfo.BindTableId > 0 {
if err := tableDependencyService.HasDependencyError(ctx, querySets[i].QuerySetInfo.BindTableId); err != nil {
return err
}
if err := dao.TableSoftDelete(ptr.transactionContext, querySets[i].QuerySetInfo.BindTableId, domain.TableType(querySets[i].Type)); err != nil {
return err
}
}
}
// 日志
if len(querySets) > 0 {
... ... @@ -353,7 +562,7 @@ func insertQuerySetsByIndex(list []*domain.QuerySet, item *domain.QuerySet, inde
func (ptr *QuerySetService) DependencyGraph(ctx *domain.Context, querySetId int) (interface{}, error) {
tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
_, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "tableTypes": []string{domain.SchemaTable.ToString(), domain.SubProcessTable.ToString()}})
dependencies := make([]dependency, 0)
var dependencies TableDependTree = NewTableDependTree(nil, nil)
if err != nil {
return dependencies, err
}
... ... @@ -368,89 +577,10 @@ func (ptr *QuerySetService) DependencyGraph(ctx *domain.Context, querySetId int)
if querySet.QuerySetInfo.BindTableId == 0 {
return dependencies, nil
}
dependencies = makeDependencyGraph(querySet.QuerySetInfo.BindTableId, tables)
return dependencies, nil
}
func makeDependencyGraph(bindTableId int, tables []*domain.Table) []dependency {
dependencies := make([]dependency, 0)
tableMap := make(map[int]*domain.Table)
graph := make(map[int][]int, 0)
for i := range tables {
tableMap[tables[i].TableId] = tables[i]
graph[tables[i].TableId] = tables[i].TableInfo.DependencyTables
}
// parent depend
dependTables := []int{bindTableId}
foundDependency := make(map[string]bool, 0)
for {
if len(dependTables) == 0 {
break
}
parent := dependTables[0]
parentTable, ok := tableMap[parent]
if !ok {
continue
}
for _, dependChildId := range parentTable.TableInfo.DependencyTables {
dependChild, ok := tableMap[dependChildId]
if !ok {
continue
}
depend := NewDependency(parentTable.TableId, dependChild)
if _, ok := foundDependency[depend.String()]; !ok {
dependencies = append(dependencies, depend)
}
}
dependTables = dependTables[1:]
}
// dependToChild
return dependencies
}
func dependParents(tables []*domain.Table, tableMap map[int]*domain.Table, bindTableId int) []int {
foundDependTable := make(map[int]bool)
traceStack := []int{bindTableId}
res := []int{bindTableId}
for {
if len(traceStack) == 0 {
break
}
last := traceStack[0]
traceStack = traceStack[1:]
table := tableMap[last]
if table == nil {
continue
}
// for
if _, ok := foundDependTable[last]; !ok {
foundDependTable[last] = true
}
}
return res
}
func NewDependency(parentId int, child *domain.Table) dependency {
return dependency{
Id: parentId,
}
}
type dependency struct {
// 标识
Id int `json:"id"`
// 表类型 MainTable:主表 SideTable:副表 SubTable:分表 ExcelTable:Excel表
Type string `json:"type"`
// 名称
Name string `json:"name"`
// 依赖的表
DependChildId int `json:"dependChildId"`
}
func (d dependency) String() string {
return fmt.Sprintf("%d-%d", d.Id, d.DependChildId)
tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext)
dependencies = tableDependencyService.TableDependTree(tables, querySet.QuerySetInfo.BindTableId)
return dependencies, nil
}
func (ptr *QuerySetService) GetAllChild(ctx *domain.Context, querySetId int, includeSelf bool, onlyNextLevel bool) ([]*domain.QuerySet, error) {
... ...
package domainService
import (
"github.com/stretchr/testify/assert"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"testing"
)
func TestSelectsEditLog(t *testing.T) {
oldQueryComponents := []*domain.QueryComponent{
&domain.QueryComponent{
Id: "1",
Selects: []domain.SelectExprGroup{
{
SelectExpr: domain.SelectExpr{
Id: "c_1",
FieldLeft: domain.FieldExpr{
ExprSql: "t.a",
ExprHuman: "T.a",
},
FieldRight: domain.FieldExpr{
ExprSql: "t.b",
ExprHuman: "T.b",
},
},
SubSelects: []domain.SelectExpr{
{
Id: "c_s_1",
FieldLeft: domain.FieldExpr{
ExprSql: "t.a",
ExprHuman: "T.a",
},
FieldRight: domain.FieldExpr{
ExprSql: "t.b",
ExprHuman: "T.b",
},
},
},
},
{
SelectExpr: domain.SelectExpr{
Id: "c_2",
FieldLeft: domain.FieldExpr{
ExprSql: "t.c",
ExprHuman: "T.c",
},
FieldRight: domain.FieldExpr{
ExprSql: "t.d",
ExprHuman: "T.d",
},
},
},
},
},
}
newQueryComponents := []*domain.QueryComponent{
&domain.QueryComponent{
Id: "1",
Selects: []domain.SelectExprGroup{
{
SelectExpr: domain.SelectExpr{
Id: "c_1",
FieldLeft: domain.FieldExpr{
ExprSql: "t.a",
ExprHuman: "T.a",
},
FieldRight: domain.FieldExpr{
ExprSql: "t.c",
ExprHuman: "T.c",
},
},
},
},
},
}
querySet := &domain.QuerySet{
QuerySetId: 1,
Type: domain.SchemaTable.ToString(),
Flag: domain.FlagSet,
Name: "测试集合",
QueryComponents: oldQueryComponents,
}
ctx := &domain.Context{CompanyId: 1, OperatorId: 1, OperatorName: "test"}
logs := selectsEditLog(ctx, querySet, newQueryComponents)
assert.Equal(t, len(logs), 3)
}
func TestConditionsEditLog(t *testing.T) {
oldQueryComponents := []*domain.QueryComponent{
&domain.QueryComponent{
Id: "1",
Conditions: []domain.ConditionExpr{
{
Id: "c_1",
FieldLeft: domain.FieldExpr{
ExprSql: "t.a",
ExprHuman: "T.a",
},
FieldRight: domain.FieldExpr{
ExprSql: "t.b",
ExprHuman: "T.b",
},
OperatorSymbol: "=",
},
{
Id: "c_2",
FieldLeft: domain.FieldExpr{
ExprSql: "t.c",
ExprHuman: "T.c",
},
FieldRight: domain.FieldExpr{
ExprSql: "t.d",
ExprHuman: "T.d",
},
OperatorSymbol: "=",
},
},
},
}
newQueryComponents := []*domain.QueryComponent{
&domain.QueryComponent{
Id: "1",
Conditions: []domain.ConditionExpr{
{
Id: "c_1",
FieldLeft: domain.FieldExpr{
ExprSql: "t.a",
ExprHuman: "T.a",
},
FieldRight: domain.FieldExpr{
ExprSql: "t.c",
ExprHuman: "T.c",
},
OperatorSymbol: "=",
},
},
},
}
querySet := &domain.QuerySet{
QuerySetId: 1,
Type: domain.SchemaTable.ToString(),
Flag: domain.FlagSet,
Name: "测试集合",
QueryComponents: oldQueryComponents,
}
ctx := &domain.Context{CompanyId: 1, OperatorId: 1, OperatorName: "test"}
logs := conditionsEditLog(ctx, querySet, newQueryComponents)
assert.Equal(t, len(logs), 2)
}
... ...
... ... @@ -64,8 +64,12 @@ func (ptr *DeleteDataTableService) DeleteTables(ctx *domain.Context, tables ...*
return nil
}
tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext)
var tableIds []int
for _, t := range tables {
if err := tableDependencyService.HasDependencyError(ctx, t.TableId); err != nil {
return err
}
if _, err := tableRepository.Remove(t); err != nil {
return err
}
... ...
package domainService
import (
"errors"
"fmt"
pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
"github.com/zeromicro/go-zero/core/collection"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/utils"
)
type DependencyError struct {
t *domain.Table
}
func (d DependencyError) Error() string {
return fmt.Sprintf("已关联%v:%v", domain.EnumsDescription(domain.ObjectTypeMap, d.t.TableType), d.t.Name)
}
type CircleDependError struct {
circleTable *domain.Table
querySet *domain.QuerySet
}
func (d CircleDependError) Error() string {
circleTableName := ""
if d.circleTable != nil {
circleTableName = d.circleTable.Name
}
return fmt.Sprintf("循环引用%s(%s)", domain.EnumsDescription(domain.ObjectTypeMap, d.querySet.Type), circleTableName)
}
func NewCircleDependError(t *domain.Table, qs *domain.QuerySet) CircleDependError {
return CircleDependError{
circleTable: t,
querySet: qs,
}
}
type TableDependencyService struct {
transactionContext *pgTransaction.TransactionContext
DetectedCycleCallBack func()
DebugLog func(string)
CircleNode int
TableMap map[int]*domain.Table
}
func NewTableDependencyService(transactionContext *pgTransaction.TransactionContext) (*TableDependencyService, error) {
if transactionContext == nil {
return nil, fmt.Errorf("transactionContext参数不能为nil")
} else {
return &TableDependencyService{
transactionContext: transactionContext,
}, nil
}
}
func (ptr *TableDependencyService) CircleTable() *domain.Table {
if ptr.TableMap == nil {
return nil
}
return ptr.TableMap[ptr.CircleNode]
}
func (ptr *TableDependencyService) HasDependencyError(ctx *domain.Context, dependencyTable int) error {
table, ok := TableHasDependency(ptr.transactionContext, ctx, dependencyTable)
if !ok {
return nil
}
return DependencyError{table}
}
func (ptr *TableDependencyService) Detect(ctx *domain.Context, edges [][]int) bool {
graph, nodes := ptr.BuildGraph(edges)
visiting := map[int]bool{}
visited := map[int]bool{}
for _, node := range nodes {
ptr.log("------------------------------------------\n")
ptr.log(fmt.Sprintf("dfs node %v \n", node))
if ptr.DetectCycle(graph, node, visiting, visited) {
return true
}
}
return false
}
func (ptr *TableDependencyService) BuildGraph(edges [][]int) (map[int][]int, []int) {
var graph = make(map[int][]int)
var nodes []int
for _, edge := range edges {
a, b := edge[0], edge[1]
if _, ok := graph[a]; !ok {
nodes = append(nodes, a)
graph[a] = make([]int, 0)
}
graph[a] = append(graph[a], b)
}
return graph, nodes
}
func (ptr *TableDependencyService) DetectCycle(graph map[int][]int, node int, visiting, visited map[int]bool) bool {
if _, found := visited[node]; found {
ptr.log(fmt.Sprintf("node %d is already visited(black) -> skip\n", node))
return false
}
if _, found := visiting[node]; found {
ptr.log(fmt.Sprintf("node %d is in visiting(gray) -> a cycle is detected\n\n", node))
ptr.CircleNode = node
return true
}
visiting[node] = true
ptr.log(fmt.Sprintf("nodes in visiting(gray): %+v\n", visiting))
ptr.log(fmt.Sprintf("nodes in visited(black): %+v\n\n", visited))
for _, descendant := range graph[node] {
ptr.log(fmt.Sprintf("current node: node %d\n", node))
ptr.log(fmt.Sprintf("visit descendant: node %d\n", descendant))
if ptr.DetectCycle(graph, descendant, visiting, visited) {
return true
}
}
delete(visiting, node)
visited[node] = true
ptr.log(fmt.Sprintf("finish explore node %d\n", node))
ptr.log(fmt.Sprintf("nodes in visiting(gray): %+v\n", visiting))
ptr.log(fmt.Sprintf("nodes in visited(black): %+v\n\n", visited))
return false
}
func (ptr *TableDependencyService) log(text string) {
if ptr.DebugLog == nil {
return
}
ptr.DebugLog(text)
}
func (ptr *TableDependencyService) TableDependTree(tables []*domain.Table, bindTableId int) TableDependTree {
var (
parentMap = ptr.makeParentMap(tables)
childMap = ptr.makeChildMap(tables)
rootNodes = ptr.findRootNodes(parentMap, bindTableId)
tree = ptr.makeTrees(childMap, rootNodes)
)
tableMap := make(map[int]*domain.Table)
for i := range tables {
tableMap[tables[i].TableId] = tables[i]
}
ptr.TableMap = tableMap
tableDependTree := NewTableDependTree(tree, tableMap)
tableDependTree.NodeSelected = NewTableNode(tableMap[bindTableId])
return tableDependTree
}
func (ptr *TableDependencyService) makeParentMap(tables []*domain.Table) map[int][]int {
res := make(map[int][]int)
for i := range tables {
if _, ok := res[tables[i].TableId]; !ok {
res[tables[i].TableId] = make([]int, 0)
}
for _, t := range tables[i].TableInfo.DependencyTables {
if _, ok := res[t]; !ok {
res[t] = make([]int, 0)
}
res[t] = append(res[t], tables[i].TableId)
}
}
return res
}
func (ptr *TableDependencyService) makeChildMap(tables []*domain.Table) map[int][]int {
res := make(map[int][]int)
for i := range tables {
id := tables[i].TableId
if _, ok := res[id]; !ok {
res[id] = make([]int, 0)
}
res[id] = append(res[id], tables[i].TableInfo.DependencyTables...)
}
return res
}
func (ptr *TableDependencyService) findRootNodes(parentsMap map[int][]int, nodeId int) []int {
var rootNodes []int
set := collection.NewSet()
stack := utils.NewEmptyStack()
stack.Push(nodeId)
for {
if stack.Size() == 0 {
break
}
t := stack.Pop().(int)
if set.Contains(t) {
continue
}
set.Add(t)
parents, ok := parentsMap[t]
if !ok {
continue
}
if len(parents) == 0 {
rootNodes = append(rootNodes, t)
}
for _, item := range parents {
stack.Push(item)
}
}
return rootNodes
}
func (ptr *TableDependencyService) makeTrees(childMap map[int][]int, rootNodes []int) []int {
set := collection.NewSet()
stack := utils.NewEmptyStack()
for _, node := range rootNodes {
stack.Push(node)
}
for {
if stack.Size() == 0 {
break
}
t := stack.Pop().(int)
if set.Contains(t) {
continue
}
set.Add(t)
parents, ok := childMap[t]
if !ok {
continue
}
if len(parents) == 0 {
rootNodes = append(rootNodes, t)
}
for _, item := range parents {
stack.Push(item)
}
}
return set.KeysInt()
}
func TableHasDependency(transactionContext *pgTransaction.TransactionContext, ctx *domain.Context, dependencyTable int) (*domain.Table, bool) {
tableRepository, _ := repository.NewTableRepository(transactionContext)
table, err := tableRepository.FindOne(map[string]interface{}{"context": ctx, "dependencyTable": dependencyTable})
if errors.Is(err, domain.ErrorNotFound) {
return nil, false
}
if table == nil && err != nil {
return nil, false
}
return table, true
}
type TableDependTree struct {
Tree []int `json:"-"`
Nodes []TableNode `json:"nodes"`
Edges []TableEdge `json:"edges"`
NodeSelected TableNode `json:"nodeSelected"`
}
func (td TableDependTree) EdgesArray() [][]int {
var res = make([][]int, 0)
for _, edge := range td.Edges {
res = append(res, []int{edge.Id, edge.DependChildId})
}
return res
}
type TableNode struct {
TableId int `json:"tableId"`
// 表类型 MainTable:主表 SideTable:副表 SubTable:分表
Type string `json:"type"`
// 名称
Name string `json:"name"`
// 依赖关联的表
DependencyTables []int `json:"-"`
}
type TableEdge struct {
// 标识
Id int `json:"tableId"`
// 表类型 MainTable:主表 SideTable:副表 SubTable:分表
Type string `json:"type"`
// 名称
Name string `json:"name"`
// 依赖的表
DependChildId int `json:"dependTableId"`
}
func NewTableDependTree(tree []int, tableMap map[int]*domain.Table) TableDependTree {
dependTree := TableDependTree{
Nodes: make([]TableNode, 0),
Edges: make([]TableEdge, 0),
Tree: tree,
}
for _, node := range tree {
t, ok := tableMap[node]
if !ok {
continue
}
dependTree.Nodes = append(dependTree.Nodes, NewTableNode(t))
for _, item := range t.DependencyTables() {
dependTable, ok := tableMap[item]
if !ok {
continue
}
dependTree.Edges = append(dependTree.Edges, NewTableEdge(t, dependTable))
}
}
return dependTree
}
func NewTableNode(table *domain.Table) TableNode {
return TableNode{
TableId: table.TableId,
Type: table.TableType,
Name: table.Name,
DependencyTables: table.TableInfo.DependencyTables,
}
}
func NewTableEdge(table *domain.Table, dependTable *domain.Table) TableEdge {
return TableEdge{
Id: table.TableId,
Type: table.TableType,
Name: table.Name,
DependChildId: dependTable.TableId,
}
}
... ...
package domainService
import (
"fmt"
"github.com/stretchr/testify/assert"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"sort"
"testing"
)
func TestTableDependTree(t *testing.T) {
/*
5 8
/ \ / \
1 3 9 10
/ / \ / \
4 6 2 11 12
\
7
*/
tables := []*domain.Table{
newTable(5, 1, 3),
newTable(1, 4),
newTable(4),
newTable(3, 6, 2),
newTable(6),
newTable(2, 7),
newTable(7),
newTable(8, 9, 10),
newTable(10),
newTable(9, 11, 12),
newTable(11),
newTable(12, 9),
}
tableDependencyService := &TableDependencyService{DebugLog: func(s string) {
//t.Log(s)
}}
inputs := []struct {
BindTableId int
Trees []int
Circle bool
}{
{
3,
[]int{1, 2, 3, 4, 5, 6, 7},
false,
},
{
4,
[]int{1, 2, 3, 4, 5, 6, 7},
false,
},
{
12,
[]int{8, 9, 10, 11, 12},
true,
},
{
8,
[]int{8, 9, 10, 11, 12},
true,
},
{
10,
[]int{8, 9, 10, 11, 12},
true,
},
}
for _, input := range inputs {
tree := tableDependencyService.TableDependTree(tables, input.BindTableId)
got := tree.Tree
sort.Ints(got)
assert.Equal(t, got, input.Trees)
assert.Equal(t, tableDependencyService.Detect(nil, tree.EdgesArray()), input.Circle)
}
}
func newTable(id int, dependTables ...int) *domain.Table {
return &domain.Table{
TableId: id,
Name: fmt.Sprintf("t%d", id),
TableInfo: &domain.TableInfo{
DependencyTables: dependTables,
},
}
}
... ...
... ... @@ -55,6 +55,13 @@ func (ptr *UpdateTableStructService) UpdateTableStruct(ctx *domain.Context, tabl
if _, err = tableRepository.Save(mainTable); err != nil {
return nil, err
}
// 字段被删除
if len(deletes) > 0 {
tableDependencyService, _ := NewTableDependencyService(ptr.transactionContext)
if err := tableDependencyService.HasDependencyError(ctx, tableId); err != nil {
return nil, err
}
}
// Log
// 日志
... ...
... ... @@ -144,6 +144,9 @@ func (repository *TableRepository) FindOne(queryOptions map[string]interface{})
if v, ok := queryOptions["tableTypes"]; ok && len(v.([]string)) > 0 {
query.Where(`table_type in (?)`, pg.In(v.([]string)))
}
if v, ok := queryOptions["dependencyTable"]; ok && v.(int) > 0 {
query.Where(`table_info->'dependencyTables' @> '[?]'`, v.(int))
}
if err := query.First(); err != nil {
if err.Error() == "pg: no rows in result set" {
return nil, domain.ErrorNotFound
... ... @@ -166,6 +169,9 @@ func (repository *TableRepository) Find(queryOptions map[string]interface{}) (in
query.SetWhereByQueryOption(fmt.Sprintf("name like '%%%v%%'", queryOptions["name"]), "name")
query.SetWhereByQueryOption("parent_id = ?", "parentId")
if v, ok := queryOptions["tableIds"]; ok && len(v.([]int)) > 0 {
query.Where(`table_id in (?)`, pg.In(v.([]int)))
}
if v, ok := queryOptions["tableTypes"]; ok && len(v.([]string)) > 0 {
query.Where(`table_type in (?)`, pg.In(v.([]string)))
}
... ...
package utils
import "sync"
// Item interface to store any data type in stack
type Item interface{}
// Stack struct which contains a list of Items
type Stack struct {
items []Item
mutex sync.Mutex
}
// NewEmptyStack() returns a new instance of Stack with zero elements
func NewEmptyStack() *Stack {
return &Stack{
items: nil,
}
}
// NewStack() returns a new instance of Stack with list of specified elements
func NewStack(items []Item) *Stack {
return &Stack{
items: items,
}
}
// Push() adds new item to top of existing/empty stack
func (stack *Stack) Push(item Item) {
stack.mutex.Lock()
defer stack.mutex.Unlock()
stack.items = append(stack.items, item)
}
// Pop() removes most recent item(top) from stack
func (stack *Stack) Pop() Item {
stack.mutex.Lock()
defer stack.mutex.Unlock()
if len(stack.items) == 0 {
return nil
}
lastItem := stack.items[len(stack.items)-1]
stack.items = stack.items[:len(stack.items)-1]
return lastItem
}
// IsEmpty() returns whether the stack is empty or not (boolean result)
func (stack *Stack) IsEmpty() bool {
stack.mutex.Lock()
defer stack.mutex.Unlock()
return len(stack.items) == 0
}
// Top() returns the last inserted item in stack without removing it.
func (stack *Stack) Top() Item {
stack.mutex.Lock()
defer stack.mutex.Unlock()
if len(stack.items) == 0 {
return nil
}
return stack.items[len(stack.items)-1]
}
// Size() returns the number of items currently in the stack
func (stack *Stack) Size() int {
stack.mutex.Lock()
defer stack.mutex.Unlock()
return len(stack.items)
}
// Clear() removes all items from the stack
func (stack *Stack) Clear() {
stack.mutex.Lock()
defer stack.mutex.Unlock()
stack.items = nil
}
... ...
... ... @@ -22,7 +22,7 @@ func (controller *QuerySetController) CreateQuerySet() {
func (controller *QuerySetController) UpdateQuerySet() {
querySetService := service.NewQuerySetService(nil)
updateQuerySetCommand := &command.UpdateQuerySetCommand{}
controller.Unmarshal(updateQuerySetCommand)
Must(controller.Unmarshal(updateQuerySetCommand))
Id, _ := controller.GetInt(":querySetId")
updateQuerySetCommand.QuerySetId = Id
data, err := querySetService.UpdateQuerySet(ParseContext(controller.BaseController), updateQuerySetCommand)
... ...