table_dependency_service.go 9.5 KB
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"
	"strings"
)

type DependencyError struct {
	DependentTables []*domain.Table
}

func (d DependencyError) Error() string {
	var relations = make([]string, 0)
	for _, t := range d.DependentTables {
		relations = append(relations, fmt.Sprintf("%v%v", domain.EnumsDescription(domain.ObjectTypeMap, t.TableType), t.Name))
	}
	return fmt.Sprintf("引用对象:%v", strings.Join(relations, ","))
}

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 {
	tables, ok := TableHasDependency(ptr.transactionContext, ctx, dependencyTable)
	if !ok {
		return nil
	}
	if len(tables) > 0 {
		return DependencyError{tables}
	}
	return nil
}

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)
	_, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "dependencyTable": dependencyTable})
	if errors.Is(err, domain.ErrorNotFound) {
		return nil, false
	}
	if tables == nil && err != nil {
		return nil, false
	}
	return tables, 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
}

func (td TableDependTree) TableIds() []int {
	var set = collection.NewSet()
	for i := range td.Nodes {
		set.Add(td.Nodes[i].TableId)
	}
	return set.KeysInt()
}

func (td *TableDependTree) BindQuerySet(querySets []*domain.QuerySet) {
	for i := range td.Nodes {
		for j := range querySets {
			if td.Nodes[i].TableId == querySets[j].QuerySetInfo.BindTableId {
				td.Nodes[i].QuerySetId = querySets[j].QuerySetId
			}
		}
	}
	for j := range querySets {
		if td.NodeSelected.TableId == querySets[j].QuerySetInfo.BindTableId {
			td.NodeSelected.QuerySetId = querySets[j].QuerySetId
		}
	}
}

type TableNode struct {
	QuerySetId int `json:"querySetId"`
	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,
	}
}