🐛 fix(tdengine): 补齐超级表元数据查询

- 表列表合并 SHOW TABLES 与 SHOW STABLES 结果
- 返回前统一去重并排序,确保超级表可见
- 增加 TDEngine 表列表回归测试

Fixes #346
This commit is contained in:
Syngnat
2026-04-17 13:14:08 +08:00
parent 22e4299d3e
commit 035f536e8d
3 changed files with 122 additions and 8 deletions

View File

@@ -35,6 +35,7 @@
| #338 | 连接clickhouse不能通过8132端口 | Fixed | Pending |
| #342 | 数据同步功能不能用mysql数据库8.4版本选了结构同步,最后没同步成功 | Fixed | Pending |
| #343 | redis删除hash类型中的key报错 | Fixed | Pending |
| #346 | TDEngine只显示子表不显示超级表 | Fixed | Pending |
| #351 | 为什么没有截断和清空表的功能呀? | Fixed | Pending |
## Notes
@@ -123,6 +124,12 @@
- 处理:前端改为传单元素数组;后端再增加一层参数归一化,兼容单字符串、字符串数组和 `[]interface{}` 三种形态,避免旧调用或异常入参再次在绑定层直接失败。
- 验证:新增 `internal/app/methods_redis_test.go` 回归测试,覆盖单字符串与字符串数组两种调用形态,并执行 `go test ./internal/app -count=1``frontend``npm run build`
### #346
- 根因:`TDengineDB.GetTables` 只查询 `SHOW TABLES`,没有把 `SHOW STABLES` 的超级表结果并入返回列表,导致 Sidebar 和依赖表列表的导出链路都只能看到子表。
- 处理:为 TDEngine 表列表查询补充 `SHOW STABLES`,与 `SHOW TABLES` 结果统一去重合并后返回,保证普通表和超级表同时可见。
- 验证:新增 `internal/db/tdengine_applychanges_test.go` 回归测试,覆盖 `GetTables` 返回普通表 + 超级表,并执行 `go test -tags gonavi_tdengine_driver ./internal/db -count=1`
### #330
- 根因:查询结果表格已经支持拖拽调整列宽,但 resize handle 没有提供双击自适应逻辑,导致用户只能靠手工拖拽慢慢试宽度。

View File

@@ -7,6 +7,8 @@ import (
"database/sql"
"database/sql/driver"
"fmt"
"io"
"reflect"
"strings"
"sync"
"testing"
@@ -24,9 +26,10 @@ var (
)
type tdengineRecordingState struct {
mu sync.Mutex
queries []string
execErr error
mu sync.Mutex
queries []string
execErr error
queryResults map[string]tdengineQueryResult
}
func (s *tdengineRecordingState) snapshotQueries() []string {
@@ -37,6 +40,12 @@ func (s *tdengineRecordingState) snapshotQueries() []string {
return queries
}
type tdengineQueryResult struct {
columns []string
rows [][]driver.Value
err error
}
type tdengineRecordingDriver struct{}
func (tdengineRecordingDriver) Open(name string) (driver.Conn, error) {
@@ -78,6 +87,50 @@ func (c *tdengineRecordingConn) ExecContext(_ context.Context, query string, arg
var _ driver.ExecerContext = (*tdengineRecordingConn)(nil)
func (c *tdengineRecordingConn) QueryContext(_ context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
if len(args) > 0 {
return nil, fmt.Errorf("unexpected query args: %d", len(args))
}
c.state.mu.Lock()
defer c.state.mu.Unlock()
c.state.queries = append(c.state.queries, query)
if result, ok := c.state.queryResults[query]; ok {
if result.err != nil {
return nil, result.err
}
return &tdengineRecordingRows{columns: result.columns, rows: result.rows}, nil
}
return &tdengineRecordingRows{}, nil
}
var _ driver.QueryerContext = (*tdengineRecordingConn)(nil)
type tdengineRecordingRows struct {
columns []string
rows [][]driver.Value
index int
}
func (r *tdengineRecordingRows) Columns() []string {
return append([]string(nil), r.columns...)
}
func (r *tdengineRecordingRows) Close() error { return nil }
func (r *tdengineRecordingRows) Next(dest []driver.Value) error {
if r.index >= len(r.rows) {
return io.EOF
}
row := r.rows[r.index]
for idx := range dest {
if idx < len(row) {
dest[idx] = row[idx]
}
}
r.index++
return nil
}
func openTDengineRecordingDB(t *testing.T) (*sql.DB, *tdengineRecordingState) {
t.Helper()
registerTDengineRecordingDriverOnce.Do(func() {
@@ -87,7 +140,7 @@ func openTDengineRecordingDB(t *testing.T) (*sql.DB, *tdengineRecordingState) {
tdengineRecordingDriverMu.Lock()
tdengineRecordingDriverSeq++
dsn := fmt.Sprintf("tdengine-recording-%d", tdengineRecordingDriverSeq)
state := &tdengineRecordingState{}
state := &tdengineRecordingState{queryResults: map[string]tdengineQueryResult{}}
tdengineRecordingDriverStates[dsn] = state
tdengineRecordingDriverMu.Unlock()
@@ -166,3 +219,35 @@ func TestTDengineApplyChanges_RejectsMixedUpdatesWithoutPartialWrite(t *testing.
t.Fatalf("期望拒绝 mixed changes 时不执行任何 SQL实际=%#v", queries)
}
}
func TestTDengineGetTablesIncludesSuperTables(t *testing.T) {
t.Parallel()
dbConn, state := openTDengineRecordingDB(t)
state.mu.Lock()
state.queryResults["SHOW TABLES FROM `metrics`"] = tdengineQueryResult{
columns: []string{"name"},
rows: [][]driver.Value{
{"d001"},
{"d002"},
},
}
state.queryResults["SHOW STABLES FROM `metrics`"] = tdengineQueryResult{
columns: []string{"name"},
rows: [][]driver.Value{
{"meters"},
},
}
state.mu.Unlock()
td := &TDengineDB{conn: dbConn}
tables, err := td.GetTables("metrics")
if err != nil {
t.Fatalf("GetTables returned error: %v", err)
}
want := []string{"d001", "d002", "meters"}
if !reflect.DeepEqual(tables, want) {
t.Fatalf("unexpected tables: got=%v want=%v", tables, want)
}
}

View File

@@ -202,13 +202,17 @@ func (t *TDengineDB) GetDatabases() ([]string, error) {
}
func (t *TDengineDB) GetTables(dbName string) ([]string, error) {
queries := make([]string, 0, 2)
queries := make([]string, 0, 4)
if strings.TrimSpace(dbName) != "" {
queries = append(queries, fmt.Sprintf("SHOW TABLES FROM `%s`", escapeBacktickIdent(dbName)))
queries = append(queries, fmt.Sprintf("SHOW STABLES FROM `%s`", escapeBacktickIdent(dbName)))
}
queries = append(queries, "SHOW TABLES")
queries = append(queries, "SHOW STABLES")
var lastErr error
tableSet := make(map[string]struct{})
tables := make([]string, 0)
for _, query := range queries {
data, _, err := t.Query(query)
if err != nil {
@@ -216,17 +220,35 @@ func (t *TDengineDB) GetTables(dbName string) ([]string, error) {
continue
}
var tables []string
for _, row := range data {
if val, ok := getValueFromRow(row, "table_name", "tablename", "name", "Table", "table"); ok {
tables = append(tables, fmt.Sprintf("%v", val))
tableName := strings.TrimSpace(fmt.Sprintf("%v", val))
if tableName == "" {
continue
}
if _, exists := tableSet[tableName]; exists {
continue
}
tableSet[tableName] = struct{}{}
tables = append(tables, tableName)
continue
}
for _, val := range row {
tables = append(tables, fmt.Sprintf("%v", val))
tableName := strings.TrimSpace(fmt.Sprintf("%v", val))
if tableName == "" {
break
}
if _, exists := tableSet[tableName]; exists {
break
}
tableSet[tableName] = struct{}{}
tables = append(tables, tableName)
break
}
}
}
if len(tables) > 0 {
sort.Strings(tables)
return tables, nil
}