github.com/GuanceCloud/cliutils@v1.1.21/pipeline/ptinput/refertable/table.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  // Package refertable for saving external data
     7  package refertable
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/spf13/cast"
    16  )
    17  
    18  const (
    19  	columnTypeStr   = "string"
    20  	columnTypeInt   = "int"
    21  	columnTypeFloat = "float"
    22  	columnTypeBool  = "bool"
    23  )
    24  
    25  type PlReferTables interface {
    26  	Query(tableName string, colName []string, colValue []any, kGet []string) (map[string]any, bool)
    27  	updateAll(tables []referTable) (retErr error)
    28  	Stats() *ReferTableStats
    29  }
    30  
    31  type PlReferTablesInMemory struct {
    32  	tables       map[string]*referTable
    33  	tablesName   []string
    34  	updateMutex  sync.Mutex
    35  	queryRWmutex sync.RWMutex
    36  }
    37  
    38  type ReferTableStats struct {
    39  	Name []string
    40  	Row  []int
    41  }
    42  
    43  func (plrefer *PlReferTablesInMemory) Query(tableName string, colName []string, colValue []any,
    44  	kGet []string,
    45  ) (map[string]any, bool) {
    46  	plrefer.queryRWmutex.RLock()
    47  	defer plrefer.queryRWmutex.RUnlock()
    48  
    49  	var table *referTable
    50  
    51  	if tableName == "" {
    52  		return nil, false
    53  	}
    54  
    55  	table = plrefer.tables[tableName]
    56  	if table == nil {
    57  		return nil, false
    58  	}
    59  
    60  	var row []any
    61  	if allRow, ok := query(table.index, colName, colValue, 1); ok {
    62  		row = table.RowData[allRow[0]]
    63  	} else {
    64  		return nil, false
    65  	}
    66  
    67  	result := map[string]any{}
    68  
    69  	if len(kGet) != 0 {
    70  		for _, key := range kGet {
    71  			cIdx, ok := table.colIndex[key]
    72  			if !ok {
    73  				continue
    74  			}
    75  			result[key] = row[cIdx]
    76  		}
    77  	} else {
    78  		for k, v := range table.colIndex {
    79  			result[k] = row[v]
    80  		}
    81  	}
    82  
    83  	return result, true
    84  }
    85  
    86  func (plrefer *PlReferTablesInMemory) updateAll(tables []referTable) (retErr error) {
    87  	defer func() {
    88  		if err := recover(); err != nil {
    89  			retErr = fmt.Errorf("run pl: %s", err)
    90  		}
    91  	}()
    92  
    93  	plrefer.updateMutex.Lock()
    94  	defer plrefer.updateMutex.Unlock()
    95  
    96  	refTableMap := map[string]*referTable{}
    97  	tablesName := []string{}
    98  	for idx := range tables {
    99  		table := tables[idx]
   100  		if err := table.buildTableIndex(); err != nil {
   101  			return err
   102  		}
   103  		if _, ok := refTableMap[table.TableName]; !ok {
   104  			refTableMap[table.TableName] = &table
   105  			tablesName = append(tablesName, table.TableName)
   106  		}
   107  	}
   108  
   109  	plrefer.queryRWmutex.Lock()
   110  	defer plrefer.queryRWmutex.Unlock()
   111  	plrefer.tables = refTableMap
   112  	plrefer.tablesName = tablesName
   113  	return nil
   114  }
   115  
   116  func (plrefer *PlReferTablesInMemory) Stats() *ReferTableStats {
   117  	plrefer.queryRWmutex.RLock()
   118  	defer plrefer.queryRWmutex.RUnlock()
   119  
   120  	tableStats := ReferTableStats{}
   121  	for _, name := range plrefer.tablesName {
   122  		tableStats.Name = append(tableStats.Name, name)
   123  
   124  		tableStats.Row = append(tableStats.Row,
   125  			len(plrefer.tables[name].RowData))
   126  	}
   127  
   128  	return &tableStats
   129  }
   130  
   131  type referTable struct {
   132  	TableName  string   `json:"table_name"`
   133  	ColumnName []string `json:"column_name"`
   134  	ColumnType []string `json:"column_type"`
   135  	RowData    [][]any  `json:"row_data"`
   136  
   137  	// 要求 []int 中的行号递增
   138  	index map[string]map[any][]int
   139  
   140  	colIndex map[string]int
   141  }
   142  
   143  func (table *referTable) check() error {
   144  	if table.TableName == "" {
   145  		return fmt.Errorf("table: \"%s\", error: empty table name", table.TableName)
   146  	}
   147  	if len(table.ColumnName) != len(table.ColumnType) {
   148  		return fmt.Errorf("table: %s, error: len(table.ColumnName) != len(table.ColumnType)",
   149  			table.TableName)
   150  	}
   151  
   152  	for idx, row := range table.RowData {
   153  		if len(table.ColumnName) != len(row) {
   154  			return fmt.Errorf("table: %s, col: %d, error: len(table.ColumnName) != len(table.RowData[%d])",
   155  				table.TableName, idx, idx)
   156  		}
   157  	}
   158  
   159  	for idx, columnName := range table.ColumnName {
   160  		if columnName == "" {
   161  			return fmt.Errorf("table: %s, column: %v, index: %d, value: \"\"",
   162  				table.TableName, table.ColumnName, idx)
   163  		}
   164  	}
   165  
   166  	for idx, columnType := range table.ColumnType {
   167  		switch columnType {
   168  		case columnTypeInt, columnTypeFloat,
   169  			columnTypeBool, columnTypeStr:
   170  		default:
   171  			return fmt.Errorf("table: %s, unsupported column type: %s -> %s",
   172  				table.TableName, table.ColumnName[idx], columnType)
   173  		}
   174  	}
   175  
   176  	return nil
   177  }
   178  
   179  func (table *referTable) buildTableIndex() error {
   180  	if err := table.check(); err != nil {
   181  		return err
   182  	}
   183  
   184  	table.index = map[string]map[any][]int{}
   185  	table.colIndex = map[string]int{}
   186  
   187  	// 遍历行
   188  	for rowIdx, row := range table.RowData {
   189  		// 遍历列,建立索引: colName -> colValue -> []rowIndex
   190  		for colIdx := 0; colIdx < len(table.ColumnName); colIdx++ {
   191  			colName := table.ColumnName[colIdx]
   192  
   193  			if _, ok := table.index[colName]; !ok {
   194  				table.colIndex[colName] = colIdx
   195  				table.index[colName] = map[any][]int{}
   196  			}
   197  
   198  			// 列数据转换为指定类型
   199  			v, err := conv(row[colIdx], table.ColumnType[colIdx])
   200  			if err != nil {
   201  				return fmt.Errorf("table: %s, row: %d, col: %d, cast error: %w",
   202  					table.TableName, rowIdx, colIdx, err)
   203  			}
   204  			row[colIdx] = v
   205  			// column 反向索引: colValue -> []rowIndex
   206  			table.index[colName][v] = append(table.index[colName][v],
   207  				rowIdx)
   208  		}
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func conv(col any, dtype string) (any, error) {
   215  	switch strings.ToLower(dtype) {
   216  	case columnTypeBool:
   217  		return cast.ToBoolE(col)
   218  	case columnTypeInt:
   219  		return cast.ToInt64E(col)
   220  	case columnTypeFloat:
   221  		return cast.ToFloat64E(col)
   222  	case columnTypeStr:
   223  		return cast.ToStringE(col)
   224  	default:
   225  		return nil, fmt.Errorf("unsupported type: %s", dtype)
   226  	}
   227  }
   228  
   229  func decodeJSONData(data []byte) ([]referTable, error) {
   230  	var tables []referTable
   231  	if err := json.Unmarshal(data, &tables); err != nil {
   232  		return nil, err
   233  	} else {
   234  		return tables, nil
   235  	}
   236  }