🐛 fix(dameng): 修复索引元数据查询与错误提示

修正 ALL_IND_COLUMNS 与 ALL_INDEXES 的索引归属关联;保留复合索引顺序、唯一性与索引类型元数据;索引加载失败时显示错误,并补齐回归测试和多语言文案。

Fixes #735
This commit is contained in:
Syngnat
2026-07-27 22:57:06 +08:00
parent 8ed3fb9413
commit c192b6cc3a
13 changed files with 237 additions and 35 deletions

View File

@@ -61,6 +61,19 @@ func (damengColumnsMetadataConn) QueryContext(_ context.Context, query string, _
}, nil
}
if strings.Contains(query, "all_ind_columns") {
if !strings.Contains(query, "i.owner = c.index_owner AND i.index_name = c.index_name") {
return nil, errors.New("ALL_IND_COLUMNS must join ALL_INDEXES through INDEX_OWNER")
}
return &damengColumnsMetadataRows{
columns: []string{"INDEX_NAME", "COLUMN_NAME", "UNIQUENESS", "COLUMN_POSITION", "INDEX_TYPE"},
values: [][]driver.Value{
{"IDX_ORDERS_TENANT_CREATED", "TENANT_ID", "NONUNIQUE", int64(1), "NORMAL"},
{"IDX_ORDERS_TENANT_CREATED", "CREATED_AT", "NONUNIQUE", int64(2), "NORMAL"},
},
}, nil
}
return &damengColumnsMetadataRows{
columns: []string{
"COLUMN_NAME", "DATA_TYPE", "DATA_LENGTH", "CHAR_LENGTH", "DATA_PRECISION",
@@ -160,3 +173,27 @@ func TestDamengGetColumnsKeepsBaseMetadataWhenAutoIncrementQueryFails(t *testing
t.Fatalf("unexpected fallback columns: %+v", columns)
}
}
func TestDamengGetIndexesUsesIndexOwnerJoinAndMapsColumnOrder(t *testing.T) {
resetDamengColumnsMetadataQueryState(t, false)
damengDB := &DamengDB{conn: openDamengColumnsMetadataDB(t)}
indexes, err := damengDB.GetIndexes("biz", "orders")
if err != nil {
t.Fatalf("GetIndexes returned error: %v", err)
}
if len(indexes) != 2 {
t.Fatalf("unexpected index column count: %d", len(indexes))
}
if indexes[0].Name != "IDX_ORDERS_TENANT_CREATED" || indexes[0].ColumnName != "TENANT_ID" || indexes[0].SeqInIndex != 1 || indexes[0].IndexType != "NORMAL" {
t.Fatalf("unexpected first index column: %+v", indexes[0])
}
if indexes[1].ColumnName != "CREATED_AT" || indexes[1].SeqInIndex != 2 {
t.Fatalf("unexpected second index column: %+v", indexes[1])
}
queries := damengColumnsMetadataQueries()
if len(queries) != 1 || !strings.Contains(queries[0], "c.table_owner = 'BIZ'") || !strings.Contains(queries[0], "c.table_name = 'ORDERS'") {
t.Fatalf("expected one normalized schema index query, got=%v", queries)
}
}

View File

@@ -332,40 +332,11 @@ func (d *DamengDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi
}
func (d *DamengDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {
query := fmt.Sprintf(`SELECT index_name, column_name, uniqueness
FROM all_ind_columns
JOIN all_indexes USING (index_name, owner)
WHERE table_owner = '%s' AND table_name = '%s'`,
strings.ToUpper(dbName), strings.ToUpper(tableName))
if dbName == "" {
query = fmt.Sprintf(`SELECT index_name, column_name, uniqueness
FROM user_ind_columns
JOIN user_indexes USING (index_name)
WHERE table_name = '%s'`, strings.ToUpper(tableName))
}
data, _, err := d.Query(query)
data, _, err := d.Query(buildDamengIndexesQuery(dbName, tableName))
if err != nil {
return nil, err
}
var indexes []connection.IndexDefinition
for _, row := range data {
unique := 1
if val, ok := row["UNIQUENESS"]; ok && val == "UNIQUE" {
unique = 0
}
idx := connection.IndexDefinition{
Name: fmt.Sprintf("%v", row["INDEX_NAME"]),
ColumnName: fmt.Sprintf("%v", row["COLUMN_NAME"]),
NonUnique: unique,
IndexType: "BTREE",
}
indexes = append(indexes, idx)
}
return indexes, nil
return buildDamengIndexDefinitions(data), nil
}
func (d *DamengDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) {

View File

@@ -188,6 +188,59 @@ func applyDamengAutoIncrementColumns(columns []connection.ColumnDefinition, data
return columns
}
func buildDamengIndexesQuery(dbName, tableName string) string {
upperDBName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(dbName)), "'", "''")
upperTableName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(tableName)), "'", "''")
if upperDBName == "" {
return fmt.Sprintf(`SELECT c.index_name, c.column_name, i.uniqueness, c.column_position, i.index_type
FROM user_ind_columns c
JOIN user_indexes i ON i.index_name = c.index_name
WHERE c.table_name = '%s'
AND c.column_name IS NOT NULL
ORDER BY c.index_name, c.column_position`, upperTableName)
}
return fmt.Sprintf(`SELECT c.index_name, c.column_name, i.uniqueness, c.column_position, i.index_type
FROM all_ind_columns c
JOIN all_indexes i ON i.owner = c.index_owner AND i.index_name = c.index_name
WHERE c.table_owner = '%s'
AND c.table_name = '%s'
AND c.column_name IS NOT NULL
ORDER BY c.index_name, c.column_position`, upperDBName, upperTableName)
}
func buildDamengIndexDefinitions(data []map[string]interface{}) []connection.IndexDefinition {
indexes := make([]connection.IndexDefinition, 0, len(data))
for _, row := range data {
name := getDamengRowString(row, "INDEX_NAME")
columnName := getDamengRowString(row, "COLUMN_NAME")
if name == "" || columnName == "" {
continue
}
nonUnique := 1
if strings.EqualFold(getDamengRowString(row, "UNIQUENESS"), "UNIQUE") {
nonUnique = 0
}
seqInIndex, _ := getDamengRowInt(row, "COLUMN_POSITION")
indexType := getDamengRowString(row, "INDEX_TYPE")
if indexType == "" {
indexType = "BTREE"
}
indexes = append(indexes, connection.IndexDefinition{
Name: name,
ColumnName: columnName,
NonUnique: nonUnique,
SeqInIndex: seqInIndex,
IndexType: indexType,
})
}
return indexes
}
func buildDamengForeignKeysQuery(dbName, tableName string) string {
upperDBName := strings.ToUpper(strings.TrimSpace(dbName))
upperTableName := strings.ToUpper(strings.TrimSpace(tableName))

View File

@@ -166,3 +166,80 @@ func TestBuildDamengColumnDefinitions_MapsComment(t *testing.T) {
t.Fatalf("expected comment to be mapped, got=%q", columns[0].Comment)
}
}
func TestBuildDamengIndexesQuery_JoinsAllViewsByIndexOwner(t *testing.T) {
t.Parallel()
query := buildDamengIndexesQuery("app", "orders")
if !strings.Contains(query, "JOIN all_indexes i ON i.owner = c.index_owner AND i.index_name = c.index_name") {
t.Fatalf("expected schema query to join ALL_INDEXES through INDEX_OWNER, got: %s", query)
}
if !strings.Contains(query, "i.index_type") {
t.Fatalf("expected index type metadata to be selected, got: %s", query)
}
if strings.Contains(strings.ToLower(query), "using (index_name, owner)") {
t.Fatalf("ALL_IND_COLUMNS exposes INDEX_OWNER rather than OWNER, got: %s", query)
}
if !strings.Contains(query, "c.table_owner = 'APP'") || !strings.Contains(query, "c.table_name = 'ORDERS'") {
t.Fatalf("expected normalized schema and table predicates, got: %s", query)
}
if !strings.Contains(query, "ORDER BY c.index_name, c.column_position") {
t.Fatalf("expected stable multi-column index ordering, got: %s", query)
}
}
func TestBuildDamengIndexesQuery_UsesUserViewsWithoutSchema(t *testing.T) {
t.Parallel()
query := buildDamengIndexesQuery("", "orders")
if !strings.Contains(query, "FROM user_ind_columns c") {
t.Fatalf("expected current-schema index columns view, got: %s", query)
}
if !strings.Contains(query, "JOIN user_indexes i ON i.index_name = c.index_name") {
t.Fatalf("expected current-schema index metadata join, got: %s", query)
}
}
func TestBuildDamengIndexDefinitions_MapsCaseInsensitiveMultiColumnMetadata(t *testing.T) {
t.Parallel()
indexes := buildDamengIndexDefinitions([]map[string]interface{}{
{
"index_name": "IDX_ORDERS_TENANT_CREATED",
"column_name": "TENANT_ID",
"uniqueness": "NONUNIQUE",
"column_position": 1,
"index_type": "NORMAL",
},
{
"INDEX_NAME": "IDX_ORDERS_TENANT_CREATED",
"COLUMN_NAME": "CREATED_AT",
"UNIQUENESS": "NONUNIQUE",
"COLUMN_POSITION": "2",
},
{
"INDEX_NAME": "UK_ORDERS_NUMBER",
"COLUMN_NAME": "ORDER_NUMBER",
"UNIQUENESS": "UNIQUE",
"COLUMN_POSITION": 1,
},
})
if len(indexes) != 3 {
t.Fatalf("expected three index columns, got=%d", len(indexes))
}
if indexes[0].Name != "IDX_ORDERS_TENANT_CREATED" || indexes[0].ColumnName != "TENANT_ID" || indexes[0].NonUnique != 1 || indexes[0].SeqInIndex != 1 {
t.Fatalf("unexpected first index column: %#v", indexes[0])
}
if indexes[0].IndexType != "NORMAL" {
t.Fatalf("expected index type metadata to be preserved, got: %#v", indexes[0])
}
if indexes[1].SeqInIndex != 2 {
t.Fatalf("expected second column position, got: %#v", indexes[1])
}
if indexes[2].NonUnique != 0 {
t.Fatalf("expected UNIQUE metadata to map to NonUnique=0, got: %#v", indexes[2])
}
}