From de26949a5eea29699ba54683eb28147d2b8a06a9 Mon Sep 17 00:00:00 2001 From: AutumnNazi <104422820+AutumnNazi@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:44:16 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(dameng):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=AE=BE=E8=AE=A1=E8=A1=A8=E6=97=A0=E6=B3=95=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E8=A1=A8=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从达梦表注释字典视图补充读取表备注 - 将表注释安全追加到建表 DDL,并保留元数据查询失败降级 - 增加 schema、转义、去重及真实 GetCreateStatement 回归测试 --- internal/db/dameng_columns_runtime_test.go | 43 ++++++++++++++++++++++ internal/db/dameng_impl.go | 12 +++++- internal/db/dameng_metadata.go | 37 +++++++++++++++++++ internal/db/dameng_metadata_test.go | 31 ++++++++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/internal/db/dameng_columns_runtime_test.go b/internal/db/dameng_columns_runtime_test.go index d5b0b78b..66312be8 100644 --- a/internal/db/dameng_columns_runtime_test.go +++ b/internal/db/dameng_columns_runtime_test.go @@ -51,6 +51,22 @@ func (damengColumnsMetadataConn) QueryContext(_ context.Context, query string, _ failAutoIncrementQuery := damengColumnsMetadataQueryState.failAutoIncrementQuery damengColumnsMetadataQueryState.Unlock() + if strings.Contains(query, "DBMS_METADATA.GET_DDL") { + return &damengColumnsMetadataRows{ + columns: []string{"DDL"}, + values: [][]driver.Value{{`CREATE TABLE "BIZ"."ORDERS" ( + "ID" NUMBER NOT NULL +)`}}, + }, nil + } + + if strings.Contains(query, "all_tab_comments") { + return &damengColumnsMetadataRows{ + columns: []string{"TABLE_COMMENT"}, + values: [][]driver.Value{{"订单'归档"}}, + }, nil + } + if strings.Contains(query, "SYS.SYSCOLUMNS") { if failAutoIncrementQuery { return nil, errors.New("insufficient privilege for SYS.SYSCOLUMNS") @@ -174,6 +190,33 @@ func TestDamengGetColumnsKeepsBaseMetadataWhenAutoIncrementQueryFails(t *testing } } +func TestDamengGetCreateStatementAppendsTableComment(t *testing.T) { + resetDamengColumnsMetadataQueryState(t, false) + + damengDB := &DamengDB{conn: openDamengColumnsMetadataDB(t)} + ddl, err := damengDB.GetCreateStatement("biz", "orders") + if err != nil { + t.Fatalf("GetCreateStatement returned error: %v", err) + } + + for _, want := range []string{ + `CREATE TABLE "BIZ"."ORDERS"`, + `COMMENT ON TABLE "BIZ"."ORDERS" IS '订单''归档';`, + } { + if !strings.Contains(ddl, want) { + t.Fatalf("expected DDL to contain %q, got: %s", want, ddl) + } + } + + queries := damengColumnsMetadataQueries() + if len(queries) != 2 || !strings.Contains(queries[1], "all_tab_comments") { + t.Fatalf("expected DDL and table comment metadata queries, got=%v", queries) + } + if !strings.Contains(queries[1], "owner = 'BIZ'") || !strings.Contains(queries[1], "table_name = 'ORDERS'") { + t.Fatalf("expected normalized schema and table comment predicates, got=%s", queries[1]) + } +} + func TestDamengGetIndexesUsesIndexOwnerJoinAndMapsColumnOrder(t *testing.T) { resetDamengColumnsMetadataQueryState(t, false) diff --git a/internal/db/dameng_impl.go b/internal/db/dameng_impl.go index 6852c64a..32b121bf 100644 --- a/internal/db/dameng_impl.go +++ b/internal/db/dameng_impl.go @@ -305,7 +305,17 @@ func (d *DamengDB) GetCreateStatement(dbName, tableName string) (string, error) if len(data) > 0 { if val, ok := data[0]["DDL"]; ok { - return fmt.Sprintf("%v", val), nil + ddl := fmt.Sprintf("%v", val) + commentData, _, commentErr := d.Query(buildDamengTableCommentQuery(dbName, tableName)) + if commentErr != nil { + logger.Warnf("达梦 GetCreateStatement 表注释元数据查询失败,已返回基础 DDL:%v", commentErr) + return ddl, nil + } + if len(commentData) == 0 { + return ddl, nil + } + comment := getDamengRowString(commentData[0], "TABLE_COMMENT", "COMMENT", "COMMENTS") + return appendDamengTableCommentDDL(ddl, dbName, tableName, comment), nil } } return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) diff --git a/internal/db/dameng_metadata.go b/internal/db/dameng_metadata.go index 0b9a95f5..f963fd70 100644 --- a/internal/db/dameng_metadata.go +++ b/internal/db/dameng_metadata.go @@ -148,6 +148,43 @@ func buildDamengColumnsQuery(dbName, tableName string) string { ORDER BY c.column_id`, upperDBName, upperTableName, upperDBName, upperTableName, upperDBName, upperTableName) } +func buildDamengTableCommentQuery(dbName, tableName string) string { + upperTableName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(tableName)), "'", "''") + upperDBName := strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(dbName)), "'", "''") + if upperDBName == "" { + return fmt.Sprintf(`SELECT comments AS "TABLE_COMMENT" + FROM user_tab_comments + WHERE table_name = '%s' AND comments IS NOT NULL`, upperTableName) + } + return fmt.Sprintf(`SELECT comments AS "TABLE_COMMENT" + FROM all_tab_comments + WHERE owner = '%s' AND table_name = '%s' AND comments IS NOT NULL`, upperDBName, upperTableName) +} + +func appendDamengTableCommentDDL(ddl, dbName, tableName, comment string) string { + baseDDL := strings.TrimSpace(ddl) + comment = strings.TrimSpace(comment) + if baseDDL == "" || comment == "" || strings.Contains(strings.ToUpper(baseDDL), "COMMENT ON TABLE ") { + return baseDDL + } + + quoteIdentifier := func(value string) string { + value = strings.ToUpper(strings.TrimSpace(value)) + return `"` + strings.ReplaceAll(value, `"`, `""`) + `"` + } + tableRef := quoteIdentifier(tableName) + if strings.TrimSpace(dbName) != "" { + tableRef = quoteIdentifier(dbName) + "." + tableRef + } + + baseDDL = strings.TrimRight(baseDDL, " \t\r\n") + if !strings.HasSuffix(baseDDL, ";") && !strings.HasSuffix(baseDDL, "/") { + baseDDL += ";" + } + escapedComment := strings.ReplaceAll(comment, "'", "''") + return fmt.Sprintf("%s\n\nCOMMENT ON TABLE %s IS '%s';", baseDDL, tableRef, escapedComment) +} + // buildDamengAutoIncrementColumnsQuery reads the stable system-table flag that // records both IDENTITY and AUTO_INCREMENT columns. It intentionally remains a // separate query so restricted accounts can still load base column metadata. diff --git a/internal/db/dameng_metadata_test.go b/internal/db/dameng_metadata_test.go index fd910e8a..97a9ae7a 100644 --- a/internal/db/dameng_metadata_test.go +++ b/internal/db/dameng_metadata_test.go @@ -146,6 +146,37 @@ func TestBuildDamengColumnsQuery_IncludesColumnCommentsJoin(t *testing.T) { } } +func TestBuildDamengTableCommentQueryUsesSchemaAppropriateDictionaryView(t *testing.T) { + t.Parallel() + + userQuery := buildDamengTableCommentQuery("", "orders") + if !strings.Contains(userQuery, "FROM user_tab_comments") || !strings.Contains(userQuery, "table_name = 'ORDERS'") { + t.Fatalf("expected current-schema table comment query, got: %s", userQuery) + } + + allQuery := buildDamengTableCommentQuery("biz", "orders") + if !strings.Contains(allQuery, "FROM all_tab_comments") || !strings.Contains(allQuery, "owner = 'BIZ'") || !strings.Contains(allQuery, "table_name = 'ORDERS'") { + t.Fatalf("expected schema table comment query, got: %s", allQuery) + } +} + +func TestAppendDamengTableCommentDDLAvoidsDuplicateAndEscapesLiteral(t *testing.T) { + t.Parallel() + + ddl := appendDamengTableCommentDDL(`CREATE TABLE "BIZ"."ORDERS" ("ID" NUMBER)`, "biz", "orders", "订单'归档") + if !strings.Contains(ddl, `COMMENT ON TABLE "BIZ"."ORDERS" IS '订单''归档';`) { + t.Fatalf("expected escaped table comment DDL, got: %s", ddl) + } + if !strings.Contains(ddl, "(\"ID\" NUMBER);\n\nCOMMENT ON TABLE") { + t.Fatalf("expected create statement terminator before table comment, got: %s", ddl) + } + + duplicated := appendDamengTableCommentDDL(ddl, "biz", "orders", "新备注") + if duplicated != ddl { + t.Fatalf("expected existing table comment DDL to remain unchanged, got: %s", duplicated) + } +} + func TestBuildDamengColumnDefinitions_MapsComment(t *testing.T) { t.Parallel()