From 13b2a61c1b8ef229988156ed12cd7726a2b90f3e Mon Sep 17 00:00:00 2001 From: metowolf Date: Tue, 28 Oct 2025 21:36:12 +0800 Subject: [PATCH] Fix field default value quoting and add column comment support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add needsQuotedDefault() method to determine if field type requires quoted default values - Fix default value formatting for string types (char, varchar, text, etc.) - Add ColumnComment field to FieldInfo struct and include in schema comparison - Update field comparison to check column comments - Add comprehensive test cases for various field types with default values - Import slices package for type checking This fixes issues where: 1. String type default values were not properly quoted in DDL statements 2. Column comments were not synced between source and target databases 3. Field comparison could miss comment differences 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/db.go | 40 +++++++++++++++++++++++++++++--- internal/field_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/internal/db.go b/internal/db.go index b55808c..bd8c89b 100644 --- a/internal/db.go +++ b/internal/db.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "log" + "slices" "strings" "time" @@ -24,9 +25,24 @@ type FieldInfo struct { CharsetName *string `json:"character_set_name"` CollationName *string `json:"collation_name"` ColumnType string `json:"column_type"` + ColumnComment string `json:"column_comment"` Extra string `json:"extra"` } +// needsQuotedDefault returns true if the field type requires quoted default values +func (f *FieldInfo) needsQuotedDefault() bool { + // String types that need quoted default values + stringTypes := []string{ + "char", "varchar", "binary", "varbinary", + "tinyblob", "blob", "mediumblob", "longblob", + "tinytext", "text", "mediumtext", "longtext", + "enum", "set", "json", + } + + dataType := strings.ToLower(f.DataType) + return slices.Contains(stringTypes, dataType) +} + // String returns the full column definition as used in CREATE TABLE func (f *FieldInfo) String() string { var parts []string @@ -43,10 +59,18 @@ func (f *FieldInfo) String() string { // Default value if f.ColumnDefault != nil { - if strings.ToUpper(*f.ColumnDefault) == "CURRENT_TIMESTAMP" { - parts = append(parts, "DEFAULT CURRENT_TIMESTAMP") + defaultValue := *f.ColumnDefault + upperDefault := strings.ToUpper(defaultValue) + + // Special keywords that don't need quotes + if upperDefault == "CURRENT_TIMESTAMP" || upperDefault == "NULL" { + parts = append(parts, fmt.Sprintf("DEFAULT %s", upperDefault)) + } else if f.needsQuotedDefault() { + // String types need quotes + parts = append(parts, fmt.Sprintf("DEFAULT '%s'", defaultValue)) } else { - parts = append(parts, fmt.Sprintf("DEFAULT %s", *f.ColumnDefault)) + // Numeric types don't need quotes + parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue)) } } @@ -55,6 +79,13 @@ func (f *FieldInfo) String() string { parts = append(parts, strings.ToUpper(f.Extra)) } + // Comment + if f.ColumnComment != "" { + // Escape single quotes in comment by doubling them + escapedComment := strings.ReplaceAll(f.ColumnComment, "'", "''") + parts = append(parts, fmt.Sprintf("COMMENT '%s'", escapedComment)) + } + return strings.Join(parts, " ") } @@ -68,6 +99,7 @@ func (f *FieldInfo) Equals(other *FieldInfo) bool { if f.ColumnName != other.ColumnName || f.IsNullAble != other.IsNullAble || f.DataType != other.DataType || + f.ColumnComment != other.ColumnComment || f.Extra != other.Extra { return false } @@ -260,6 +292,7 @@ func (db *MyDb) TableFieldsFromInformationSchema(tableName string) (map[string]* CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE, + COLUMN_COMMENT, EXTRA FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? @@ -290,6 +323,7 @@ func (db *MyDb) TableFieldsFromInformationSchema(tableName string) (map[string]* &charset, &collation, &field.ColumnType, + &field.ColumnComment, &field.Extra, ) if err != nil { diff --git a/internal/field_test.go b/internal/field_test.go index 86e4db5..8a62bf9 100644 --- a/internal/field_test.go +++ b/internal/field_test.go @@ -308,6 +308,7 @@ func TestFieldInfo_String(t *testing.T) { ColumnName: "name", ColumnType: "varchar(64)", IsNullAble: "NO", + DataType: "varchar", CharsetName: stringPtr("utf8mb4"), CollationName: stringPtr("utf8mb4_general_ci"), }, @@ -319,18 +320,68 @@ func TestFieldInfo_String(t *testing.T) { ColumnName: "status", ColumnType: "tinyint", IsNullAble: "NO", + DataType: "tinyint", ColumnDefault: stringPtr("0"), CharsetName: nil, CollationName: nil, }, want: "`status` tinyint NOT NULL DEFAULT 0", }, + { + name: "varchar field with string default value", + field: &FieldInfo{ + ColumnName: "f_status", + ColumnType: "varchar(32)", + IsNullAble: "NO", + DataType: "varchar", + ColumnDefault: stringPtr("queue"), + CharsetName: stringPtr("utf8mb3"), + CollationName: stringPtr("utf8mb3_general_ci"), + }, + want: "`f_status` varchar(32) NOT NULL DEFAULT 'queue'", + }, + { + name: "char field with string default value", + field: &FieldInfo{ + ColumnName: "type", + ColumnType: "char(10)", + IsNullAble: "NO", + DataType: "char", + ColumnDefault: stringPtr("active"), + }, + want: "`type` char(10) NOT NULL DEFAULT 'active'", + }, + { + name: "text field with string default value", + field: &FieldInfo{ + ColumnName: "description", + ColumnType: "text", + IsNullAble: "YES", + DataType: "text", + ColumnDefault: stringPtr("default text"), + CharsetName: stringPtr("utf8mb4"), + CollationName: stringPtr("utf8mb4_general_ci"), + }, + want: "`description` text NULL DEFAULT 'default text'", + }, + { + name: "int field with numeric default value", + field: &FieldInfo{ + ColumnName: "count", + ColumnType: "int", + IsNullAble: "NO", + DataType: "int", + ColumnDefault: stringPtr("100"), + }, + want: "`count` int NOT NULL DEFAULT 100", + }, { name: "nullable field", field: &FieldInfo{ ColumnName: "description", ColumnType: "text", IsNullAble: "YES", + DataType: "text", CharsetName: stringPtr("utf8mb4"), CollationName: stringPtr("utf8mb4_general_ci"), }, @@ -342,6 +393,7 @@ func TestFieldInfo_String(t *testing.T) { ColumnName: "updated_at", ColumnType: "timestamp", IsNullAble: "NO", + DataType: "timestamp", ColumnDefault: stringPtr("CURRENT_TIMESTAMP"), Extra: "on update CURRENT_TIMESTAMP", CharsetName: nil,