fix(pgsql): strip backticks from column defaults in migration paths
Backtick-wrapped defaults (e.g. from GORM tags like `now()`) were only stripped in the CREATE TABLE column-definition path, leaving raw backticks in the ALTER COLUMN ... SET DEFAULT migration statement and in the migration-generated CREATE TABLE template, producing invalid SQL. Default-drift comparisons also compared raw values, so a backtick-wrapped model default never matched the live DB default and kept re-emitting redundant ALTER statements.
This commit is contained in:
@@ -460,7 +460,7 @@ func (w *MigrationWriter) generateAlterTableScripts(schema *models.Schema, model
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check default value changes
|
// Check default value changes
|
||||||
if fmt.Sprintf("%v", modelCol.Default) != fmt.Sprintf("%v", currentCol.Default) {
|
if !columnDefaultsEqual(modelCol.Default, currentCol.Default) {
|
||||||
setDefault, defaultVal := formatColumnDefaultSQL(modelCol)
|
setDefault, defaultVal := formatColumnDefaultSQL(modelCol)
|
||||||
|
|
||||||
sql, err := w.executor.ExecuteAlterColumnDefaultWithCheck(AlterColumnDefaultWithCheckData{
|
sql, err := w.executor.ExecuteAlterColumnDefaultWithCheck(AlterColumnDefaultWithCheckData{
|
||||||
@@ -956,7 +956,24 @@ func columnsEqual(col1, col2 *models.Column) bool {
|
|||||||
}
|
}
|
||||||
return columnTypesEqual(col1, col2) &&
|
return columnTypesEqual(col1, col2) &&
|
||||||
col1.NotNull == col2.NotNull &&
|
col1.NotNull == col2.NotNull &&
|
||||||
fmt.Sprintf("%v", col1.Default) == fmt.Sprintf("%v", col2.Default)
|
columnDefaultsEqual(col1.Default, col2.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
// columnDefaultsEqual compares column defaults for drift detection, stripping
|
||||||
|
// MySQL-style backticks (e.g. from GORM tags) so a model default of
|
||||||
|
// "`now()`" is recognised as equal to a live default of "now()".
|
||||||
|
func columnDefaultsEqual(default1, default2 interface{}) bool {
|
||||||
|
return normalizeDefaultForCompare(default1) == normalizeDefaultForCompare(default2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeDefaultForCompare(value interface{}) string {
|
||||||
|
if value == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if s, ok := value.(string); ok {
|
||||||
|
return strings.TrimSpace(stripBackticks(s))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func columnTypesEqual(col1, col2 *models.Column) bool {
|
func columnTypesEqual(col1, col2 *models.Column) bool {
|
||||||
|
|||||||
@@ -530,7 +530,7 @@ func BuildCreateTableData(schemaName string, table *models.Table) CreateTableDat
|
|||||||
}
|
}
|
||||||
if col.Default != nil {
|
if col.Default != nil {
|
||||||
if value, ok := col.Default.(string); ok {
|
if value, ok := col.Default.(string); ok {
|
||||||
colData.Default = writers.QuoteDefaultValue(value, col.Type)
|
colData.Default = writers.QuoteDefaultValue(stripBackticks(value), col.Type)
|
||||||
} else {
|
} else {
|
||||||
colData.Default = fmt.Sprintf("%v", col.Default)
|
colData.Default = fmt.Sprintf("%v", col.Default)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ func formatColumnDefaultSQL(col *models.Column) (setDefault bool, defaultVal str
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
if value, ok := col.Default.(string); ok {
|
if value, ok := col.Default.(string); ok {
|
||||||
return true, writers.QuoteDefaultValue(value, col.Type)
|
return true, writers.QuoteDefaultValue(stripBackticks(value), col.Type)
|
||||||
}
|
}
|
||||||
return true, fmt.Sprintf("%v", col.Default)
|
return true, fmt.Sprintf("%v", col.Default)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1140,6 +1140,37 @@ func TestWriteSchema_EmitsGuardedAlterColumnDefaultStatements(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteSchema_AlterColumnDefaultStripsBackticksFromFunctionExpression(t *testing.T) {
|
||||||
|
db := models.InitDatabase("testdb")
|
||||||
|
schema := models.InitSchema("public")
|
||||||
|
|
||||||
|
table := models.InitTable("agent_skills", "public")
|
||||||
|
|
||||||
|
updatedAtCol := models.InitColumn("updatedat", "agent_skills", "public")
|
||||||
|
updatedAtCol.Type = "timestamp"
|
||||||
|
updatedAtCol.Default = "`now()`"
|
||||||
|
table.Columns["updatedat"] = updatedAtCol
|
||||||
|
|
||||||
|
schema.Tables = append(schema.Tables, table)
|
||||||
|
db.Schemas = append(db.Schemas, schema)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&writers.WriterOptions{})
|
||||||
|
writer.writer = &buf
|
||||||
|
|
||||||
|
if err := writer.WriteDatabase(db); err != nil {
|
||||||
|
t.Fatalf("WriteDatabase failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
if strings.Contains(output, "`") {
|
||||||
|
t.Fatalf("expected no backticks in generated SQL, got:\n%s", output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "ALTER COLUMN updatedat SET DEFAULT now()") {
|
||||||
|
t.Fatalf("expected guarded SET DEFAULT now() without backticks, got:\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriteSchema_GuardedAlterColumnTypeFallsBackOnConversionFailure(t *testing.T) {
|
func TestWriteSchema_GuardedAlterColumnTypeFallsBackOnConversionFailure(t *testing.T) {
|
||||||
db := models.InitDatabase("testdb")
|
db := models.InitDatabase("testdb")
|
||||||
schema := models.InitSchema("public")
|
schema := models.InitSchema("public")
|
||||||
|
|||||||
Reference in New Issue
Block a user