From 93dc696e90891a630952e606cea091a738006da1 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Mon, 6 Jul 2026 08:56:41 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(db):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BE=BE=E6=A2=A6DDL=E7=BC=BA=E5=A4=B1=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=20Fixes=20#615?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补齐达梦列元数据查询中的字段注释读取 - 保留原始 CREATE TABLE 结果并追加 COMMENT ON COLUMN 语句 - 修正达梦列定义映射时 COMMENT 丢失的问题 - 新增达梦 DDL 注释补全回归测试 --- internal/app/methods_db.go | 48 +++++++++++++++++++ .../app/methods_db_create_statement_test.go | 33 +++++++++++++ internal/db/dameng_metadata.go | 9 +++- internal/db/dameng_metadata_test.go | 42 ++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/internal/app/methods_db.go b/internal/app/methods_db.go index 688f03b9..f3990a45 100644 --- a/internal/app/methods_db.go +++ b/internal/app/methods_db.go @@ -1766,6 +1766,9 @@ func resolveCreateStatementWithFallbackWithText(dbInst db.Database, config conne sqlStr, sourceErr := dbInst.GetCreateStatement(metadataSchemaName, metadataTableName) if sourceErr == nil && !shouldFallbackCreateStatement(dbType, sqlStr) { if strings.TrimSpace(sqlStr) != "" { + if columns, err := loadCreateStatementCommentColumns(dbInst, dbType, metadataSchemaName, metadataTableName); err == nil { + sqlStr = appendCreateStatementColumnComments(dbType, ddlSchemaName, ddlTableName, sqlStr, columns) + } return sqlStr, nil } if isOceanBaseOracleProtocol(config) { @@ -1916,6 +1919,51 @@ func buildFallbackCreateStatement(dbType string, schemaName string, tableName st return buildFallbackCreateStatementWithText(dbType, schemaName, tableName, columns, nil, defaultDBBackendText) } +func loadCreateStatementCommentColumns(dbInst db.Database, dbType string, schemaName string, tableName string) ([]connection.ColumnDefinition, error) { + if !shouldAppendColumnCommentsToCreateStatement(dbType) { + return nil, nil + } + return dbInst.GetColumns(schemaName, tableName) +} + +func shouldAppendColumnCommentsToCreateStatement(dbType string) bool { + switch dbType { + case "dameng": + return true + default: + return false + } +} + +func appendCreateStatementColumnComments(dbType string, schemaName string, tableName string, ddl string, columns []connection.ColumnDefinition) string { + if !shouldAppendColumnCommentsToCreateStatement(dbType) || strings.TrimSpace(ddl) == "" || len(columns) == 0 { + return ddl + } + + qualifiedTable := quoteTableIdentByType(dbType, schemaName, tableName) + existingDDLUpper := strings.ToUpper(ddl) + commentStatements := make([]string, 0, len(columns)) + for _, col := range columns { + commentSQL := buildFallbackColumnCommentStatement(dbType, schemaName, tableName, qualifiedTable, col.Name, col.Comment) + if commentSQL == "" { + continue + } + if strings.Contains(existingDDLUpper, strings.ToUpper(commentSQL)) { + continue + } + commentStatements = append(commentStatements, commentSQL) + } + if len(commentStatements) == 0 { + return ddl + } + + trimmedDDL := strings.TrimRight(ddl, " \t\r\n") + if !strings.HasSuffix(trimmedDDL, ";") { + trimmedDDL += ";" + } + return trimmedDDL + "\n" + strings.Join(commentStatements, "\n") +} + func buildFallbackCreateStatementWithText(dbType string, schemaName string, tableName string, columns []connection.ColumnDefinition, indexes []connection.IndexDefinition, text func(string, map[string]any) string) (string, error) { if text == nil { text = defaultDBBackendText diff --git a/internal/app/methods_db_create_statement_test.go b/internal/app/methods_db_create_statement_test.go index dde5d55e..24b7f88a 100644 --- a/internal/app/methods_db_create_statement_test.go +++ b/internal/app/methods_db_create_statement_test.go @@ -286,6 +286,39 @@ func TestResolveCreateStatementWithFallback_KingbaseIncludesColumnComments(t *te } } +func TestResolveCreateStatementWithFallback_DamengAppendsColumnComments(t *testing.T) { + t.Parallel() + + dbInst := &fakeCreateStatementDB{ + createSQL: `CREATE TABLE "APP"."ORDERS" ("ID" NUMBER NOT NULL, "STATUS" VARCHAR2(32));`, + columns: []connection.ColumnDefinition{ + {Name: "ID", Type: "NUMBER", Nullable: "NO", Key: "PRI", Comment: "主键"}, + {Name: "STATUS", Type: "VARCHAR2(32)", Nullable: "YES", Comment: "状态"}, + }, + } + + ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{ + Type: "dameng", + }, "APP", "ORDERS") + if err != nil { + t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err) + } + if !strings.Contains(ddl, dbInst.createSQL) { + t.Fatalf("expected original dameng create SQL to be preserved, got: %s", ddl) + } + for _, want := range []string{ + `COMMENT ON COLUMN "APP"."ORDERS"."ID" IS '主键';`, + `COMMENT ON COLUMN "APP"."ORDERS"."STATUS" IS '状态';`, + } { + if !strings.Contains(ddl, want) { + t.Fatalf("expected dameng DDL to contain %q, got: %s", want, ddl) + } + } + if dbInst.columnsCalls != 1 { + t.Fatalf("expected dameng path to load column metadata once, got %d", dbInst.columnsCalls) + } +} + func TestResolveCreateStatementWithFallback_KingbaseIncludesSecondaryIndexes(t *testing.T) { t.Parallel() diff --git a/internal/db/dameng_metadata.go b/internal/db/dameng_metadata.go index 394560dd..620c97f5 100644 --- a/internal/db/dameng_metadata.go +++ b/internal/db/dameng_metadata.go @@ -110,9 +110,11 @@ func buildDamengColumnsQuery(dbName, tableName string) string { upperDBName := strings.ToUpper(strings.TrimSpace(dbName)) if upperDBName == "" { - return fmt.Sprintf(`SELECT c.column_name, c.data_type, c.data_length, c.char_length, c.data_precision, c.data_scale, c.nullable, c.data_default, + return fmt.Sprintf(`SELECT c.column_name, c.data_type, c.data_length, c.char_length, c.data_precision, c.data_scale, c.nullable, c.data_default, cc.comments AS comment, CASE WHEN pk.column_name IS NOT NULL THEN 'PRI' ELSE '' END AS column_key FROM user_tab_columns c + LEFT JOIN user_col_comments cc + ON cc.table_name = c.table_name AND cc.column_name = c.column_name LEFT JOIN ( SELECT cols.table_name, cols.column_name FROM user_constraints cons @@ -125,9 +127,11 @@ func buildDamengColumnsQuery(dbName, tableName string) string { ORDER BY c.column_id`, upperTableName, upperTableName, upperTableName) } - return fmt.Sprintf(`SELECT c.column_name, c.data_type, c.data_length, c.char_length, c.data_precision, c.data_scale, c.nullable, c.data_default, + return fmt.Sprintf(`SELECT c.column_name, c.data_type, c.data_length, c.char_length, c.data_precision, c.data_scale, c.nullable, c.data_default, cc.comments AS comment, CASE WHEN pk.column_name IS NOT NULL THEN 'PRI' ELSE '' END AS column_key FROM all_tab_columns c + LEFT JOIN all_col_comments cc + ON cc.owner = c.owner AND cc.table_name = c.table_name AND cc.column_name = c.column_name LEFT JOIN ( SELECT cols.owner, cols.table_name, cols.column_name FROM all_constraints cons @@ -243,6 +247,7 @@ func buildDamengColumnDefinitions(data []map[string]interface{}) []connection.Co Type: formatDamengColumnType(row), Nullable: normalizeDamengNullable(getDamengRowString(row, "NULLABLE")), Key: getDamengRowString(row, "COLUMN_KEY"), + Comment: getDamengRowString(row, "COMMENT", "COMMENTS"), } defaultValue := getDamengRowString(row, "DATA_DEFAULT") diff --git a/internal/db/dameng_metadata_test.go b/internal/db/dameng_metadata_test.go index b5b6557d..52e4f534 100644 --- a/internal/db/dameng_metadata_test.go +++ b/internal/db/dameng_metadata_test.go @@ -3,6 +3,7 @@ package db import ( "errors" "reflect" + "strings" "testing" ) @@ -118,3 +119,44 @@ func TestCollectDamengDatabaseNames_FallbackToCurrentUser(t *testing.T) { t.Fatalf("兜底查询应该返回当前用户, got=%v want=%v", got, want) } } + +func TestBuildDamengColumnsQuery_IncludesColumnCommentsJoin(t *testing.T) { + t.Parallel() + + userQuery := buildDamengColumnsQuery("", "orders") + if !strings.Contains(userQuery, "user_col_comments") { + t.Fatalf("expected user query to join user_col_comments, got: %s", userQuery) + } + if !strings.Contains(userQuery, "cc.comments AS comment") { + t.Fatalf("expected user query to select column comments, got: %s", userQuery) + } + + allQuery := buildDamengColumnsQuery("app", "orders") + if !strings.Contains(allQuery, "all_col_comments") { + t.Fatalf("expected schema query to join all_col_comments, got: %s", allQuery) + } + if !strings.Contains(allQuery, "cc.comments AS comment") { + t.Fatalf("expected schema query to select column comments, got: %s", allQuery) + } +} + +func TestBuildDamengColumnDefinitions_MapsComment(t *testing.T) { + t.Parallel() + + columns := buildDamengColumnDefinitions([]map[string]interface{}{ + { + "COLUMN_NAME": "ID", + "DATA_TYPE": "NUMBER", + "NULLABLE": "N", + "COLUMN_KEY": "PRI", + "COMMENT": "主键", + }, + }) + + if len(columns) != 1 { + t.Fatalf("expected one column, got=%d", len(columns)) + } + if columns[0].Comment != "主键" { + t.Fatalf("expected comment to be mapped, got=%q", columns[0].Comment) + } +}