🐛 fix(dameng): 修复表设计字段注释加载失败

增加 SYSCOLUMNCOMMENTS 原生字典降级查询,按字段名合并注释;原生字典无权限时保留基础元数据,并补充回归测试。
This commit is contained in:
Syngnat
2026-07-30 20:44:12 +08:00
parent 3ea3223749
commit aec34a031a
4 changed files with 121 additions and 12 deletions

View File

@@ -28,6 +28,7 @@ var registerDamengColumnsMetadataDriverOnce sync.Once
var damengColumnsMetadataQueryState struct {
sync.Mutex
failAutoIncrementQuery bool
failColumnCommentQuery bool
queries []string
}
@@ -49,6 +50,7 @@ func (damengColumnsMetadataConn) QueryContext(_ context.Context, query string, _
damengColumnsMetadataQueryState.Lock()
damengColumnsMetadataQueryState.queries = append(damengColumnsMetadataQueryState.queries, query)
failAutoIncrementQuery := damengColumnsMetadataQueryState.failAutoIncrementQuery
failColumnCommentQuery := damengColumnsMetadataQueryState.failColumnCommentQuery
damengColumnsMetadataQueryState.Unlock()
if strings.Contains(query, "DBMS_METADATA.GET_DDL") {
@@ -67,6 +69,19 @@ func (damengColumnsMetadataConn) QueryContext(_ context.Context, query string, _
}, nil
}
if strings.Contains(query, "SYS.SYSCOLUMNCOMMENTS") {
if failColumnCommentQuery {
return nil, errors.New("insufficient privilege for SYS.SYSCOLUMNCOMMENTS")
}
return &damengColumnsMetadataRows{
columns: []string{"COLUMN_NAME", "COL_COMMENT"},
values: [][]driver.Value{
{"ID", "订单主键"},
{"NAME", "客户名称"},
},
}, nil
}
if strings.Contains(query, "SYS.SYSCOLUMNS") {
if failAutoIncrementQuery {
return nil, errors.New("insufficient privilege for SYS.SYSCOLUMNS")
@@ -132,16 +147,18 @@ func openDamengColumnsMetadataDB(t *testing.T) *sql.DB {
return conn
}
func resetDamengColumnsMetadataQueryState(t *testing.T, failAutoIncrementQuery bool) {
func resetDamengColumnsMetadataQueryState(t *testing.T, failAutoIncrementQuery, failColumnCommentQuery bool) {
t.Helper()
damengColumnsMetadataQueryState.Lock()
damengColumnsMetadataQueryState.failAutoIncrementQuery = failAutoIncrementQuery
damengColumnsMetadataQueryState.failColumnCommentQuery = failColumnCommentQuery
damengColumnsMetadataQueryState.queries = nil
damengColumnsMetadataQueryState.Unlock()
t.Cleanup(func() {
damengColumnsMetadataQueryState.Lock()
damengColumnsMetadataQueryState.failAutoIncrementQuery = false
damengColumnsMetadataQueryState.failColumnCommentQuery = false
damengColumnsMetadataQueryState.queries = nil
damengColumnsMetadataQueryState.Unlock()
})
@@ -154,7 +171,7 @@ func damengColumnsMetadataQueries() []string {
}
func TestDamengGetColumnsMarksAutoIncrementColumns(t *testing.T) {
resetDamengColumnsMetadataQueryState(t, false)
resetDamengColumnsMetadataQueryState(t, false, false)
damengDB := &DamengDB{conn: openDamengColumnsMetadataDB(t)}
columns, err := damengDB.GetColumns("biz", "orders")
@@ -170,15 +187,18 @@ func TestDamengGetColumnsMarksAutoIncrementColumns(t *testing.T) {
if columns[1].Extra != "" {
t.Fatalf("non-identity column should not be marked: %+v", columns[1])
}
if columns[0].Comment != "订单主键" || columns[1].Comment != "客户名称" {
t.Fatalf("native column comments should fill empty compatibility-view values: %+v", columns)
}
queries := damengColumnsMetadataQueries()
if len(queries) != 2 || !strings.Contains(queries[1], "SYS.SYSCOLUMNS") {
t.Fatalf("expected base and system metadata queries, got=%v", queries)
if len(queries) != 3 || !strings.Contains(queries[1], "SYS.SYSCOLUMNCOMMENTS") || !strings.Contains(queries[2], "SYS.SYSCOLUMNS") {
t.Fatalf("expected base, native comment, and system column metadata queries, got=%v", queries)
}
}
func TestDamengGetColumnsKeepsBaseMetadataWhenAutoIncrementQueryFails(t *testing.T) {
resetDamengColumnsMetadataQueryState(t, true)
resetDamengColumnsMetadataQueryState(t, true, false)
damengDB := &DamengDB{conn: openDamengColumnsMetadataDB(t)}
columns, err := damengDB.GetColumns("biz", "orders")
@@ -190,8 +210,21 @@ func TestDamengGetColumnsKeepsBaseMetadataWhenAutoIncrementQueryFails(t *testing
}
}
func TestDamengGetColumnsKeepsBaseMetadataWhenNativeCommentQueryFails(t *testing.T) {
resetDamengColumnsMetadataQueryState(t, false, true)
damengDB := &DamengDB{conn: openDamengColumnsMetadataDB(t)}
columns, err := damengDB.GetColumns("biz", "orders")
if err != nil {
t.Fatalf("GetColumns should keep base metadata when native comments are unavailable: %v", err)
}
if len(columns) != 2 || columns[0].Name != "ID" || columns[0].Extra != "auto_increment" {
t.Fatalf("unexpected fallback columns: %+v", columns)
}
}
func TestDamengGetCreateStatementAppendsTableComment(t *testing.T) {
resetDamengColumnsMetadataQueryState(t, false)
resetDamengColumnsMetadataQueryState(t, false, false)
damengDB := &DamengDB{conn: openDamengColumnsMetadataDB(t)}
ddl, err := damengDB.GetCreateStatement("biz", "orders")
@@ -218,7 +251,7 @@ func TestDamengGetCreateStatementAppendsTableComment(t *testing.T) {
}
func TestDamengGetIndexesUsesIndexOwnerJoinAndMapsColumnOrder(t *testing.T) {
resetDamengColumnsMetadataQueryState(t, false)
resetDamengColumnsMetadataQueryState(t, false, false)
damengDB := &DamengDB{conn: openDamengColumnsMetadataDB(t)}
indexes, err := damengDB.GetIndexes("biz", "orders")

View File

@@ -331,6 +331,14 @@ func (d *DamengDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi
if len(columns) == 0 {
return columns, nil
}
if !hasDamengColumnComments(columns) {
commentData, _, commentErr := d.Query(buildDamengColumnCommentsQuery(dbName, tableName))
if commentErr != nil {
logger.Warnf("达梦 GetColumns 原生字段注释查询失败,已返回基础字段定义:%v", commentErr)
} else {
columns = applyDamengColumnComments(columns, commentData)
}
}
autoIncrementData, _, autoIncrementErr := d.Query(buildDamengAutoIncrementColumnsQuery(dbName, tableName))
if autoIncrementErr != nil {

View File

@@ -148,6 +148,25 @@ func buildDamengColumnsQuery(dbName, tableName string) string {
ORDER BY c.column_id`, upperDBName, upperTableName, upperDBName, upperTableName, upperDBName, upperTableName)
}
// buildDamengColumnCommentsQuery uses Dameng's native comment dictionary as a
// fallback. Some DM8 deployments expose column metadata through the Oracle-
// compatible ALL_COL_COMMENTS/USER_COL_COMMENTS views but return empty comment
// values when those views are joined with the column dictionary.
func buildDamengColumnCommentsQuery(dbName, tableName string) string {
upperTableName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(tableName)), "'", "''")
upperDBName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(dbName)), "'", "''")
schemaPredicate := "SCHNAME = USER"
if upperDBName != "" {
schemaPredicate = fmt.Sprintf("SCHNAME = '%s'", upperDBName)
}
return fmt.Sprintf(`SELECT COLNAME AS column_name, COMMENT$ AS col_comment
FROM SYS.SYSCOLUMNCOMMENTS
WHERE %s AND TVNAME = '%s' AND COMMENT$ IS NOT NULL
ORDER BY COLNAME`, schemaPredicate, upperTableName)
}
func buildDamengTableCommentQuery(dbName, tableName string) string {
upperTableName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(tableName)), "'", "''")
upperDBName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(dbName)), "'", "''")
@@ -393,3 +412,30 @@ func buildDamengColumnDefinitions(data []map[string]interface{}) []connection.Co
return columns
}
func hasDamengColumnComments(columns []connection.ColumnDefinition) bool {
for _, column := range columns {
if strings.TrimSpace(column.Comment) != "" {
return true
}
}
return false
}
func applyDamengColumnComments(columns []connection.ColumnDefinition, data []map[string]interface{}) []connection.ColumnDefinition {
commentsByColumn := make(map[string]string, len(data))
for _, row := range data {
columnName := strings.ToUpper(strings.TrimSpace(getDamengRowString(row, "COLUMN_NAME", "COLNAME")))
if columnName == "" {
continue
}
commentsByColumn[columnName] = getDamengRowString(row, "COL_COMMENT", "COMMENT$", "COMMENT", "COMMENTS")
}
for i := range columns {
if comment, ok := commentsByColumn[strings.ToUpper(strings.TrimSpace(columns[i].Name))]; ok {
columns[i].Comment = comment
}
}
return columns
}

View File

@@ -146,6 +146,28 @@ func TestBuildDamengColumnsQuery_IncludesColumnCommentsJoin(t *testing.T) {
}
}
func TestBuildDamengColumnCommentsQueryUsesNativeDictionary(t *testing.T) {
t.Parallel()
userQuery := buildDamengColumnCommentsQuery("", "orders")
for _, want := range []string{
"FROM SYS.SYSCOLUMNCOMMENTS",
"SCHNAME = USER",
"TVNAME = 'ORDERS'",
"COLNAME AS column_name",
"COMMENT$ AS col_comment",
} {
if !strings.Contains(userQuery, want) {
t.Fatalf("current-schema native comment query should contain %q, got: %s", want, userQuery)
}
}
ownerQuery := buildDamengColumnCommentsQuery("biz", "orders")
if !strings.Contains(ownerQuery, "SCHNAME = 'BIZ'") || !strings.Contains(ownerQuery, "TVNAME = 'ORDERS'") {
t.Fatalf("schema native comment query should target the selected table, got: %s", ownerQuery)
}
}
func TestBuildDamengTableCommentQueryUsesSchemaAppropriateDictionaryView(t *testing.T) {
t.Parallel()
@@ -182,11 +204,11 @@ func TestBuildDamengColumnDefinitions_MapsComment(t *testing.T) {
columns := buildDamengColumnDefinitions([]map[string]interface{}{
{
"COLUMN_NAME": "ID",
"DATA_TYPE": "NUMBER",
"NULLABLE": "N",
"COLUMN_KEY": "PRI",
"COL_COMMENT": "主键",
"COLUMN_NAME": "ID",
"DATA_TYPE": "NUMBER",
"NULLABLE": "N",
"COLUMN_KEY": "PRI",
"COL_COMMENT": "主键",
},
})