query_set_service_load_calculate_set.go 7.7 KB
package domainService

import (
	"fmt"
	"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/starrocks"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log"
	"strings"
)

const DefaultExpandNum = 10000
const MaxExpandNum = 50000

func (ptr *QuerySetService) LoadCalculateSetData(ctx *domain.Context, qs *domain.QuerySet, queryComponents []*domain.QueryComponent) (*domain.DataTable, error) {
	var (
		res        = &domain.DataTable{}
		dataTables = make(map[int]*domain.DataTable)
		err        error
	)

	if len(queryComponents) == 0 {
		return res, nil
	}
	// 加载Tables数据
	q := queryComponents[0]
	cells := q.Layout.LayoutCells()
	dataTables = ptr.LoadDataTables(ctx, cells)
	// 数据布局
	res, err = DataLayout(res, dataTables, cells)
	if err != nil {
		return nil, err
	}
	// 数据持久化
	return res, nil
}

func FastTable(table *domain.Table) (*domain.DataTable, error) {
	var err error
	var options = starrocks.QueryOptions{
		Table:     table,
		TableName: table.SQLName,
		Select:    table.Fields(false),
	}
	var dataTable *domain.DataTable
	dataTable, err = FastDataTable(options)
	if err != nil {
		return nil, err
	}
	return dataTable, nil
}

func FastDataTable(options starrocks.QueryOptions) (*domain.DataTable, error) {
	var err error
	// 待优化分批下载,压缩
	var dataTable *domain.DataTable
	dataTable, err = starrocks.Query(options, starrocks.WrapQueryFuncWithDB(starrocks.DB))
	if err != nil {
		return nil, err
	}

	dataTable.Total, err = starrocks.WrapQueryCountWithDB(options, starrocks.DB)()
	if err != nil {
		return nil, err
	}
	return dataTable, nil
}

func (ptr *QuerySetService) LoadDataTables(ctx *domain.Context, cells []*domain.LayoutCell) map[int]*domain.DataTable {
	var (
		dataTables         = make(map[int]*domain.DataTable)
		tableRepository, _ = repository.NewTableRepository(ptr.transactionContext)
	)
	tableIds := collection.NewSet()
	for _, cell := range cells {
		if cell.Data == nil || cell.Data.TableField == nil || cell.Data.TableField.TableId == 0 {
			continue
		}
		tableIds.AddInt(cell.Data.TableField.TableId)
	}
	if len(tableIds.KeysInt()) > 0 {
		_, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "tableIds": tableIds.KeysInt()})
		if err != nil {
			return nil
		}
		for _, t := range tables {
			if _, ok := dataTables[t.TableId]; ok {
				continue
			}
			dataTable, e := FastTable(t)
			if e != nil {
				log.Logger.Error(e.Error())
				continue
			}
			dataTable.Fields = t.DataFields
			dataTables[t.TableId] = dataTable
		}
	}
	return dataTables
}

func DataLayout(res *domain.DataTable, dataTables map[int]*domain.DataTable, cells []*domain.LayoutCell) (*domain.DataTable, error) {
	dt := &DataLayoutDataTable{
		DataTable:     res,
		MapDataTables: dataTables,
		unprocessed:   cells,
	}
	dt.Init(DefaultExpandNum)
	for {
		if len(dt.unprocessed) == 0 {
			break
		}
		cell := dt.unprocessed[0]
		dt.unprocessed = dt.unprocessed[1:]
		blockData, length := dt.BlockData(cell)
		if err := dt.Expand(cell, length); err != nil {
			return nil, err
		}
		dt.addByLocation(cell, blockData)
		// 当前单元格子 影响其他格子坐标
		dt.changeUnProcessedLocation(cell, length)
		dt.processed = append(dt.processed, cell)
		dt.LastCell = cell
	}
	dt.Shrink()
	return dt.DataTable, nil
}

type DataLayoutDataTable struct {
	PointBegin    *Location
	PointEnd      *Location
	MaxX          int
	MaxY          int
	DataTable     *domain.DataTable
	MapDataTables map[int]*domain.DataTable
	processed     []*domain.LayoutCell
	unprocessed   []*domain.LayoutCell
	LastCell      *domain.LayoutCell
}

type Location struct {
	X int
	Y int
}

func NewLocation(x, y int) *Location {
	return &Location{
		X: x,
		Y: y,
	}
}

func (l *Location) UpdateX(x int) {
	if l.X <= x {
		l.X = x
	}
}

func (l *Location) UpdateY(y int) {
	if l.Y <= y {
		l.Y = y
	}
}

func (d *DataLayoutDataTable) StartCell() {

}

func (d *DataLayoutDataTable) addByLocation(cell *domain.LayoutCell, blockData []string) {
	if d.PointBegin == nil {
		d.PointBegin = NewLocation(cell.X, cell.Y)
		d.PointEnd = NewLocation(cell.X, cell.Y)
	}
	switch cell.Direction {
	case domain.DirectionRight:
		for i := range blockData {
			d.DataTable.Data[cell.X][cell.Y+i] = blockData[i]
		}
		d.PointEnd.UpdateX(cell.X)
		d.PointEnd.UpdateY(cell.Y + len(blockData) - 1)
	case domain.DirectionDown:
		for i := range blockData {
			d.DataTable.Data[cell.X+i][cell.Y] = blockData[i]
		}
		d.PointEnd.UpdateX(cell.X + len(blockData) - 1)
		d.PointEnd.UpdateY(cell.Y)
	case domain.DirectionNone:
		d.DataTable.Data[cell.X][cell.Y] = blockData[0]
		d.PointEnd.UpdateX(cell.X)
		d.PointEnd.UpdateY(cell.Y)
	}
}

func (d *DataLayoutDataTable) changeUnProcessedLocation(lastCell *domain.LayoutCell, length int) {
	// log.Logger.Info("修改定位点")
	for _, cell := range d.unprocessed {
		switch lastCell.Direction {
		case domain.DirectionRight:
			if cell.X >= lastCell.X && cell.Y > lastCell.Y {
				cell.Y += length - 1
			}
		case domain.DirectionDown:
			if cell.X > lastCell.X && cell.Y >= lastCell.Y {
				cell.X += length - 1
			}
		}
		// log.Logger.Info(fmt.Sprintf("%s %s X:%d Y:%d", cell.Data.Field.SQLName, cell.Direction, cell.X, cell.Y))
	}
}

func (d *DataLayoutDataTable) BlockData(cells *domain.LayoutCell) ([]string, int) {
	var block []string
	if cells.Type == domain.CellTypeText {
		data := []string{cells.Data.Text}
		return data, 1
	}
	table, ok := d.MapDataTables[cells.Data.TableField.TableId]
	if !ok {
		return block, 0
	}
	values := table.Values(&domain.Field{SQLName: cells.Data.TableField.FieldSqlName})
	if len(values) == 0 {
		return block, 0
	}
	if cells.Data.TableField != nil && strings.HasPrefix(cells.Data.TableField.TableSqlName, strings.ToLower(domain.CalculateItem.ToString())) {
		return values[:1], 1
	}
	if cells.Direction == domain.DirectionNone {
		return values[:1], 1
	}
	return values, len(values)
}

func (d *DataLayoutDataTable) Shrink() {
	x := d.PointEnd.X - d.PointBegin.X
	y := d.PointEnd.Y - d.PointBegin.Y
	data := make([][]string, x+1)
	for i := range data {
		data[i] = make([]string, y+1)
	}
	iData := 0
	for i := d.PointBegin.X; i <= d.PointEnd.X; i++ {
		jData := 0
		for j := d.PointBegin.Y; j <= d.PointEnd.Y; j++ {
			data[iData][jData] = d.DataTable.Data[i][j]
			jData++
		}
		iData++
	}
	d.DataTable.Data = data
	for i := 0; i <= y; i++ {
		d.DataTable.Fields = append(d.DataTable.Fields, &domain.Field{
			Name:    fmt.Sprintf("列%d", i),
			SQLName: fmt.Sprintf("col%d", i),
			SQLType: domain.String.ToString(),
		})
	}
}

func (d *DataLayoutDataTable) Expand(cell *domain.LayoutCell, length int) error {
	if !d.CellOutRange(cell, length) {
		return nil
	}
	expandLength := DefaultExpandNum
	if expandLength < length {
		expandLength = length
	}
	if d.MaxX+expandLength > MaxExpandNum {
		return fmt.Errorf("布局元素超过限制 %d", MaxExpandNum)
	}
	copyData := make([][]string, d.MaxX+length)
	for i := range copyData {
		copyData[i] = make([]string, d.MaxY+length)
	}
	copy(copyData, d.DataTable.Data)
	d.DataTable.Data = copyData
	return nil
}

func (d *DataLayoutDataTable) Init(length int) {
	initData := make([][]string, length)
	for i := range initData {
		initData[i] = make([]string, length)
	}
	d.DataTable.Data = initData
	d.AddRange(length)
}

func (d *DataLayoutDataTable) AddRange(length int) {
	d.MaxY += length
	d.MaxX += length
}

func (d *DataLayoutDataTable) CellOutRange(cell *domain.LayoutCell, length int) bool {
	if cell.X+length > d.MaxX {
		return true
	}
	if cell.Y+length > d.MaxY {
		return true
	}
	return false
}