mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 19:51:44 +08:00
✨ feat(editor): 完善 SQL 编辑与数据编辑交互
- 结果区状态按 SQL Tab 独立保存,快捷键可恢复手动隐藏面板 - 对象设计保留完整字段类型和可空信息,完善兼容驱动 DDL 元数据 - 数据编辑新增手动/自动提交设置和自动提交倒计时 - 修复 schema 视图定位时找不到左侧树节点的问题
This commit is contained in:
@@ -251,8 +251,8 @@ func (c *CustomDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi
|
||||
schema = dbName
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
query := fmt.Sprintf(`SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = '%s'`, tableName)
|
||||
|
||||
// Adjust for schema if likely supported
|
||||
@@ -272,30 +272,97 @@ func (c *CustomDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi
|
||||
|
||||
var columns []connection.ColumnDefinition
|
||||
for _, row := range data {
|
||||
col := connection.ColumnDefinition{}
|
||||
// flexible mapping
|
||||
for k, v := range row {
|
||||
kl := strings.ToLower(k)
|
||||
val := fmt.Sprintf("%v", v)
|
||||
if strings.Contains(kl, "field") || strings.Contains(kl, "column_name") {
|
||||
col.Name = val
|
||||
} else if strings.Contains(kl, "type") {
|
||||
col.Type = val
|
||||
} else if strings.Contains(kl, "null") || strings.Contains(kl, "nullable") {
|
||||
col.Nullable = val
|
||||
} else if strings.Contains(kl, "default") {
|
||||
col.Default = &val
|
||||
} else if strings.Contains(kl, "key") {
|
||||
col.Key = val
|
||||
} else if strings.Contains(kl, "comment") {
|
||||
col.Comment = val
|
||||
}
|
||||
}
|
||||
columns = append(columns, col)
|
||||
columns = append(columns, buildCustomColumnDefinition(row))
|
||||
}
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
func buildCustomColumnDefinition(row map[string]interface{}) connection.ColumnDefinition {
|
||||
col := connection.ColumnDefinition{
|
||||
Name: customMetadataString(row, "Field", "field", "COLUMN_NAME", "column_name", "NAME", "name"),
|
||||
Type: buildCustomColumnType(row),
|
||||
Nullable: normalizeCustomNullable(customMetadataString(row, "Null", "null", "IS_NULLABLE", "is_nullable", "NULLABLE", "nullable")),
|
||||
Key: customMetadataString(row, "Key", "key", "COLUMN_KEY", "column_key", "PRIMARY_KEY", "primary_key"),
|
||||
Extra: customMetadataString(row, "Extra", "extra", "EXTRA"),
|
||||
Comment: customMetadataString(row, "Comment", "comment", "COMMENTS", "comments", "COLUMN_COMMENT", "column_comment"),
|
||||
}
|
||||
if defaultValue, ok := customMetadataStringOK(row, "Default", "default", "COLUMN_DEFAULT", "column_default", "DATA_DEFAULT", "data_default"); ok {
|
||||
col.Default = &defaultValue
|
||||
}
|
||||
return col
|
||||
}
|
||||
|
||||
func buildCustomColumnType(row map[string]interface{}) string {
|
||||
rawType := customMetadataString(
|
||||
row,
|
||||
"COLUMN_TYPE",
|
||||
"column_type",
|
||||
"FULL_TYPE",
|
||||
"full_type",
|
||||
"FULL_DATA_TYPE",
|
||||
"full_data_type",
|
||||
"TYPE_NAME",
|
||||
"type_name",
|
||||
"Type",
|
||||
"type",
|
||||
"DATA_TYPE",
|
||||
"data_type",
|
||||
)
|
||||
if rawType == "" || strings.Contains(rawType, "(") {
|
||||
return rawType
|
||||
}
|
||||
|
||||
upperType := strings.ToUpper(rawType)
|
||||
charLength := customMetadataInt(row, "CHARACTER_MAXIMUM_LENGTH", "character_maximum_length", "CHARACTER_MAX_LENGTH", "character_max_length", "CHAR_LENGTH", "char_length", "LENGTH", "length")
|
||||
if charLength > 0 && strings.Contains(upperType, "CHAR") {
|
||||
return fmt.Sprintf("%s(%d)", rawType, charLength)
|
||||
}
|
||||
|
||||
precision := customMetadataInt(row, "NUMERIC_PRECISION", "numeric_precision", "DATA_PRECISION", "data_precision", "PRECISION", "precision")
|
||||
if precision > 0 && (strings.Contains(upperType, "DECIMAL") || strings.Contains(upperType, "NUMERIC") || strings.Contains(upperType, "NUMBER")) {
|
||||
scale := customMetadataInt(row, "NUMERIC_SCALE", "numeric_scale", "DATA_SCALE", "data_scale", "SCALE", "scale")
|
||||
if scale > 0 {
|
||||
return fmt.Sprintf("%s(%d,%d)", rawType, precision, scale)
|
||||
}
|
||||
return fmt.Sprintf("%s(%d)", rawType, precision)
|
||||
}
|
||||
|
||||
return rawType
|
||||
}
|
||||
|
||||
func customMetadataString(row map[string]interface{}, keys ...string) string {
|
||||
value, _ := customMetadataStringOK(row, keys...)
|
||||
return value
|
||||
}
|
||||
|
||||
func customMetadataStringOK(row map[string]interface{}, keys ...string) (string, bool) {
|
||||
for _, key := range keys {
|
||||
for rowKey, raw := range row {
|
||||
if !strings.EqualFold(rowKey, key) || raw == nil {
|
||||
continue
|
||||
}
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", raw)), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func customMetadataInt(row map[string]interface{}, keys ...string) int {
|
||||
return parseMetadataInt(customMetadataString(row, keys...))
|
||||
}
|
||||
|
||||
func normalizeCustomNullable(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
switch strings.ToLower(trimmed) {
|
||||
case "n", "no", "false", "0":
|
||||
return "NO"
|
||||
case "y", "yes", "true", "1":
|
||||
return "YES"
|
||||
default:
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CustomDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {
|
||||
return nil, fmt.Errorf("not implemented for custom")
|
||||
}
|
||||
|
||||
@@ -142,3 +142,44 @@ func TestCustomDBOnlyNormalizesBuiltInMySQLDriverDSN(t *testing.T) {
|
||||
t.Fatalf("non-mysql custom driver DSN should stay untouched, got %q", customMySQLDSNRecordingLastDSN)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCustomColumnDefinitionPrefersCompleteColumnType(t *testing.T) {
|
||||
col := buildCustomColumnDefinition(map[string]interface{}{
|
||||
"COLUMN_NAME": "USER_NAME",
|
||||
"DATA_TYPE": "varchar",
|
||||
"COLUMN_TYPE": "varchar(64)",
|
||||
"IS_NULLABLE": "NO",
|
||||
})
|
||||
|
||||
if col.Name != "USER_NAME" {
|
||||
t.Fatalf("expected name USER_NAME, got %q", col.Name)
|
||||
}
|
||||
if col.Type != "varchar(64)" {
|
||||
t.Fatalf("expected complete type varchar(64), got %q", col.Type)
|
||||
}
|
||||
if col.Nullable != "NO" {
|
||||
t.Fatalf("expected nullable NO, got %q", col.Nullable)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCustomColumnDefinitionBuildsTypeFromLengthAndPrecision(t *testing.T) {
|
||||
nameCol := buildCustomColumnDefinition(map[string]interface{}{
|
||||
"column_name": "display_name",
|
||||
"data_type": "varchar",
|
||||
"character_maximum_length": int64(128),
|
||||
"is_nullable": "YES",
|
||||
})
|
||||
if nameCol.Type != "varchar(128)" {
|
||||
t.Fatalf("expected varchar(128), got %q", nameCol.Type)
|
||||
}
|
||||
|
||||
amountCol := buildCustomColumnDefinition(map[string]interface{}{
|
||||
"column_name": "amount",
|
||||
"data_type": "decimal",
|
||||
"numeric_precision": float64(10),
|
||||
"numeric_scale": float64(2),
|
||||
})
|
||||
if amountCol.Type != "decimal(10,2)" {
|
||||
t.Fatalf("expected decimal(10,2), got %q", amountCol.Type)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user