fix(vastbase): tolerate NULL columns in index metadata scan

This commit is contained in:
t8y2 2026-08-07 12:57:02 +08:00
parent 9a00c10da4
commit 6d2659ac97
No known key found for this signature in database
2 changed files with 53 additions and 10 deletions

View File

@ -657,17 +657,19 @@ func (s *server) listIndexes(schema, table string) ([]indexInfo, error) {
defer rows.Close()
catalogIndexes := []vastbaseCatalogIndex{}
attributes := map[int]string{}
// UNION ALL 双分支各列可空性无法保证Vastbase G100 在某些表/索引形态下会返回 NULL
// 统一用可空类型扫描以容错 NULL避免 convertAssign 崩溃(#5602
for rows.Next() {
var rowKind, attributeNumber int
var name, kind, columnNumbers, column string
var rowKind, attributeNumber sql.NullInt64
var name, kind, columnNumbers, column sql.NullString
var unique, primary bool
if err := rows.Scan(&rowKind, &name, &kind, &unique, &primary, &columnNumbers, &attributeNumber, &column); err != nil {
return nil, err
}
if rowKind == 0 {
catalogIndexes = append(catalogIndexes, vastbaseCatalogIndex{name: name, indexType: kind, unique: unique, primary: primary, columnNumbers: columnNumbers})
} else if attributeNumber > 0 && column != "" {
attributes[attributeNumber] = column
if rowKind.Int64 == 0 {
catalogIndexes = append(catalogIndexes, vastbaseCatalogIndex{name: name.String, indexType: kind.String, unique: unique, primary: primary, columnNumbers: columnNumbers.String})
} else if attributeNumber.Int64 > 0 && column.String != "" {
attributes[int(attributeNumber.Int64)] = column.String
}
}
if err := rows.Err(); err != nil {

View File

@ -47,6 +47,43 @@ func TestVastbaseListIndexesMapsCatalogVectorsInOneQuery(t *testing.T) {
assertVastbaseIndex(t, indexes[1], "orders_pkey", []string{"id", "tenant_id"}, true, true, "btree")
}
// TestVastbaseListIndexesToleratesNullColumnName 复刻 #5602Vastbase G100 在 UNION ALL
// 索引查询中对 column_namecolumn index 7返回 NULL裸 string 扫描会报
// "converting NULL to string is unsupported"。修复后用 sql.NullString 容错NULL 行被跳过不崩。
func TestVastbaseListIndexesToleratesNullColumnName(t *testing.T) {
state := &vastbaseIndexMetadataTestState{
rows: [][]driver.Value{
// 分支1row_kind=0的 column_name 占位列也可能为 NULL不应影响索引元数据解析。
{int64(0), "orders_pkey", "btree", true, true, "1 3", int64(0), nil},
{int64(1), "", "", false, false, "", int64(1), "id"},
// 模拟 Vastbase 对某 attribute 行的 column_name 返回 NULL该行应被跳过。
{int64(1), "", "", false, false, "", int64(2), nil},
{int64(1), "", "", false, false, "", int64(3), "code"},
},
}
driverName := fmt.Sprintf("vastbase-index-null-%d", vastbaseIndexMetadataDriverSequence.Add(1))
sql.Register(driverName, &vastbaseIndexMetadataTestDriver{state: state})
db, err := sql.Open(driverName, "")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = db.Close() })
server := newServer()
server.db = db
server.mode.postgresCatalog = true
indexes, err := server.listIndexes("app", "orders")
if err != nil {
t.Fatalf("listIndexes should tolerate NULL column_name, got error: %v", err)
}
// orders_pkey 的 indkey 为 "1 3",对应 id(1) 和 code(3)attribute 2 的 NULL 行被跳过,
// 但 id 与 code 仍可解析,因此索引应正常返回。
if len(indexes) != 1 {
t.Fatalf("listIndexes returned %d indexes, want 1: %+v", len(indexes), indexes)
}
assertVastbaseIndex(t, indexes[0], "orders_pkey", []string{"id", "code"}, true, true, "btree")
}
func TestParseVastbaseAttributeNumbersSupportsCatalogRepresentations(t *testing.T) {
for _, test := range []struct {
raw string
@ -80,6 +117,8 @@ var vastbaseIndexMetadataDriverSequence atomic.Uint64
type vastbaseIndexMetadataTestState struct {
queryCount int
query string
// rows 为非空时覆盖默认返回数据,用于注入 NULL 等边界场景。
rows [][]driver.Value
}
type vastbaseIndexMetadataTestDriver struct {
@ -103,16 +142,18 @@ func (*vastbaseIndexMetadataTestConn) Begin() (driver.Tx, error) { return nil, d
func (conn *vastbaseIndexMetadataTestConn) QueryContext(_ context.Context, query string, _ []driver.NamedValue) (driver.Rows, error) {
conn.state.queryCount++
conn.state.query = query
return &vastbaseIndexMetadataTestRows{
rows: [][]driver.Value{
rows := conn.state.rows
if rows == nil {
rows = [][]driver.Value{
{int64(0), "orders_code_idx", "btree", true, false, "3 2", int64(0), ""},
{int64(0), "orders_expression_idx", "btree", false, false, "0 2", int64(0), ""},
{int64(0), "orders_pkey", "btree", true, true, "1 2", int64(0), ""},
{int64(1), "", "", false, false, "", int64(1), "id"},
{int64(1), "", "", false, false, "", int64(2), "tenant_id"},
{int64(1), "", "", false, false, "", int64(3), "code"},
},
}, nil
}
}
return &vastbaseIndexMetadataTestRows{rows: rows}, nil
}
type vastbaseIndexMetadataTestRows struct {