🐛 fix(dameng): 修正表格更新无法识别主键列

- 达梦列元数据查询补充主键关联并返回 column_key
- GetColumns 正确映射主键标记,避免表格更新退化为整行 WHERE
- 补充达梦列元数据回归测试,并验证带驱动 tag 的实现编译通过

Fixes #389
This commit is contained in:
Syngnat
2026-04-17 18:42:47 +08:00
parent 8eae39c2c2
commit dca5f629b2
3 changed files with 120 additions and 28 deletions

View File

@@ -0,0 +1,63 @@
package db
import (
"strings"
"testing"
)
func TestBuildDamengColumnsQuery_IncludesPrimaryKeyMetadata(t *testing.T) {
t.Parallel()
ownerQuery := buildDamengColumnsQuery("biz", "orders")
if !strings.Contains(ownerQuery, "constraint_type = 'P'") {
t.Fatalf("owner query 应包含主键约束过滤, got=%s", ownerQuery)
}
if !strings.Contains(ownerQuery, "AS column_key") {
t.Fatalf("owner query 应返回 column_key, got=%s", ownerQuery)
}
if !strings.Contains(ownerQuery, "WHERE c.owner = 'BIZ' AND c.table_name = 'ORDERS'") {
t.Fatalf("owner query 应按 owner/table 过滤, got=%s", ownerQuery)
}
userQuery := buildDamengColumnsQuery("", "orders")
if !strings.Contains(userQuery, "FROM user_tab_columns c") {
t.Fatalf("user query 应使用 user_tab_columns, got=%s", userQuery)
}
if !strings.Contains(userQuery, "JOIN user_cons_columns cols") {
t.Fatalf("user query 应关联 user_cons_columns, got=%s", userQuery)
}
}
func TestBuildDamengColumnDefinitions_MarksPrimaryKeyColumns(t *testing.T) {
t.Parallel()
columns := buildDamengColumnDefinitions([]map[string]interface{}{
{
"COLUMN_NAME": "ID",
"DATA_TYPE": "INTEGER",
"NULLABLE": "N",
"DATA_DEFAULT": nil,
"COLUMN_KEY": "PRI",
},
{
"COLUMN_NAME": "NAME",
"DATA_TYPE": "VARCHAR2",
"NULLABLE": "Y",
"DATA_DEFAULT": "guest",
"COLUMN_KEY": "",
},
})
if len(columns) != 2 {
t.Fatalf("unexpected column count: %d", len(columns))
}
if columns[0].Name != "ID" || columns[0].Key != "PRI" {
t.Fatalf("主键列未正确标记: %+v", columns[0])
}
if columns[1].Name != "NAME" || columns[1].Key != "" {
t.Fatalf("非主键列标记异常: %+v", columns[1])
}
if columns[1].Default == nil || *columns[1].Default != "guest" {
t.Fatalf("默认值未保留: %+v", columns[1])
}
}

View File

@@ -264,38 +264,12 @@ func (d *DamengDB) GetCreateStatement(dbName, tableName string) (string, error)
}
func (d *DamengDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
query := fmt.Sprintf(`SELECT column_name, data_type, nullable, data_default
FROM all_tab_columns
WHERE owner = '%s' AND table_name = '%s'`,
strings.ToUpper(dbName), strings.ToUpper(tableName))
if dbName == "" {
query = fmt.Sprintf(`SELECT column_name, data_type, nullable, data_default
FROM user_tab_columns
WHERE table_name = '%s'`, strings.ToUpper(tableName))
}
data, _, err := d.Query(query)
data, _, err := d.Query(buildDamengColumnsQuery(dbName, tableName))
if err != nil {
return nil, err
}
var columns []connection.ColumnDefinition
for _, row := range data {
col := connection.ColumnDefinition{
Name: fmt.Sprintf("%v", row["COLUMN_NAME"]),
Type: fmt.Sprintf("%v", row["DATA_TYPE"]),
Nullable: fmt.Sprintf("%v", row["NULLABLE"]),
}
if row["DATA_DEFAULT"] != nil {
def := fmt.Sprintf("%v", row["DATA_DEFAULT"])
col.Default = &def
}
columns = append(columns, col)
}
return columns, nil
return buildDamengColumnDefinitions(data), nil
}
func (d *DamengDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {

View File

@@ -5,6 +5,7 @@ import (
"sort"
"strings"
"GoNavi-Wails/internal/connection"
"GoNavi-Wails/internal/logger"
)
@@ -103,3 +104,57 @@ func getDamengRowString(row map[string]interface{}, keys ...string) string {
}
return ""
}
func buildDamengColumnsQuery(dbName, tableName string) string {
upperTableName := strings.ToUpper(strings.TrimSpace(tableName))
upperDBName := strings.ToUpper(strings.TrimSpace(dbName))
if upperDBName == "" {
return fmt.Sprintf(`SELECT c.column_name, c.data_type, c.nullable, c.data_default,
CASE WHEN pk.column_name IS NOT NULL THEN 'PRI' ELSE '' END AS column_key
FROM user_tab_columns c
LEFT JOIN (
SELECT cols.table_name, cols.column_name
FROM user_constraints cons
JOIN user_cons_columns cols USING (constraint_name)
WHERE cons.constraint_type = 'P'
) pk ON c.table_name = pk.table_name AND c.column_name = pk.column_name
WHERE c.table_name = '%s'
ORDER BY c.column_id`, upperTableName)
}
return fmt.Sprintf(`SELECT c.column_name, c.data_type, c.nullable, c.data_default,
CASE WHEN pk.column_name IS NOT NULL THEN 'PRI' ELSE '' END AS column_key
FROM all_tab_columns c
LEFT JOIN (
SELECT cols.owner, cols.table_name, cols.column_name
FROM all_constraints cons
JOIN all_cons_columns cols
ON cons.owner = cols.owner AND cons.constraint_name = cols.constraint_name
WHERE cons.constraint_type = 'P'
) pk ON c.owner = pk.owner AND c.table_name = pk.table_name AND c.column_name = pk.column_name
WHERE c.owner = '%s' AND c.table_name = '%s'
ORDER BY c.column_id`, upperDBName, upperTableName)
}
func buildDamengColumnDefinitions(data []map[string]interface{}) []connection.ColumnDefinition {
columns := make([]connection.ColumnDefinition, 0, len(data))
for _, row := range data {
col := connection.ColumnDefinition{
Name: getDamengRowString(row, "COLUMN_NAME"),
Type: getDamengRowString(row, "DATA_TYPE"),
Nullable: getDamengRowString(row, "NULLABLE"),
Key: getDamengRowString(row, "COLUMN_KEY"),
}
defaultValue := getDamengRowString(row, "DATA_DEFAULT")
if defaultValue != "" {
def := defaultValue
col.Default = &def
}
columns = append(columns, col)
}
return columns
}