feat: Enhance PostgreSQL type handling and migration scripts
- Introduced equivalent base types and variants for PostgreSQL types to normalize type comparisons. - Added functions for normalizing SQL types and retrieving equivalent type variants. - Updated migration writer to handle type alterations with checks for existing types. - Implemented logic to create necessary extensions (e.g., pg_trgm) based on schema requirements. - Enhanced tests to cover new functionality for type normalization and migration handling. - Improved handling of GIN indexes to use appropriate operator classes based on column types.
This commit is contained in:
@@ -22,6 +22,16 @@ type MergeResult struct {
|
||||
EnumsAdded int
|
||||
ViewsAdded int
|
||||
SequencesAdded int
|
||||
TypeConflicts []ColumnTypeConflict
|
||||
}
|
||||
|
||||
// ColumnTypeConflict describes a column that exists in both schemas but with incompatible types.
|
||||
type ColumnTypeConflict struct {
|
||||
Schema string
|
||||
Table string
|
||||
Column string
|
||||
TargetType string
|
||||
SourceType string
|
||||
}
|
||||
|
||||
// MergeOptions contains options for merge operations
|
||||
@@ -146,11 +156,19 @@ func (r *MergeResult) mergeColumns(table *models.Table, srcTable *models.Table)
|
||||
|
||||
// Merge columns
|
||||
for colName, srcCol := range srcTable.Columns {
|
||||
if _, exists := existingColumns[colName]; !exists {
|
||||
if tgtCol, exists := existingColumns[colName]; !exists {
|
||||
// Column doesn't exist, add it
|
||||
newCol := cloneColumn(srcCol)
|
||||
table.Columns[colName] = newCol
|
||||
r.ColumnsAdded++
|
||||
} else if columnTypeConflict(tgtCol, srcCol) {
|
||||
r.TypeConflicts = append(r.TypeConflicts, ColumnTypeConflict{
|
||||
Schema: firstNonEmpty(table.Schema, srcTable.Schema, srcCol.Schema),
|
||||
Table: firstNonEmpty(table.Name, srcTable.Name, srcCol.Table),
|
||||
Column: firstNonEmpty(tgtCol.Name, srcCol.Name, colName),
|
||||
TargetType: describeColumnType(tgtCol),
|
||||
SourceType: describeColumnType(srcCol),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,6 +444,52 @@ func cloneColumn(col *models.Column) *models.Column {
|
||||
return newCol
|
||||
}
|
||||
|
||||
func columnTypeConflict(target, source *models.Column) bool {
|
||||
if target == nil || source == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return normalizeType(target.Type) != normalizeType(source.Type) ||
|
||||
target.Length != source.Length ||
|
||||
target.Precision != source.Precision ||
|
||||
target.Scale != source.Scale
|
||||
}
|
||||
|
||||
func normalizeType(value string) string {
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
func describeColumnType(col *models.Column) string {
|
||||
if col == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
typeName := strings.TrimSpace(col.Type)
|
||||
if typeName == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch {
|
||||
case col.Precision > 0 && col.Scale > 0:
|
||||
return fmt.Sprintf("%s(%d,%d)", typeName, col.Precision, col.Scale)
|
||||
case col.Precision > 0:
|
||||
return fmt.Sprintf("%s(%d)", typeName, col.Precision)
|
||||
case col.Length > 0:
|
||||
return fmt.Sprintf("%s(%d)", typeName, col.Length)
|
||||
default:
|
||||
return typeName
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func cloneConstraint(constraint *models.Constraint) *models.Constraint {
|
||||
if constraint == nil {
|
||||
return nil
|
||||
@@ -609,6 +673,7 @@ func GetMergeSummary(result *MergeResult) string {
|
||||
fmt.Sprintf("Enums added: %d", result.EnumsAdded),
|
||||
fmt.Sprintf("Relations added: %d", result.RelationsAdded),
|
||||
fmt.Sprintf("Domains added: %d", result.DomainsAdded),
|
||||
fmt.Sprintf("Type conflicts: %d", len(result.TypeConflicts)),
|
||||
}
|
||||
|
||||
totalAdded := result.SchemasAdded + result.TablesAdded + result.ColumnsAdded +
|
||||
@@ -625,3 +690,35 @@ func GetMergeSummary(result *MergeResult) string {
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// GetColumnTypeConflictSummary returns a short, human-readable conflict summary.
|
||||
func GetColumnTypeConflictSummary(result *MergeResult, limit int) string {
|
||||
if result == nil || len(result.TypeConflicts) == 0 {
|
||||
return ""
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = len(result.TypeConflicts)
|
||||
}
|
||||
|
||||
lines := make([]string, 0, min(limit, len(result.TypeConflicts))+1)
|
||||
lines = append(lines, "column type conflicts detected:")
|
||||
for i, conflict := range result.TypeConflicts {
|
||||
if i >= limit {
|
||||
break
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" - %s.%s.%s: target=%s source=%s",
|
||||
conflict.Schema, conflict.Table, conflict.Column, conflict.TargetType, conflict.SourceType))
|
||||
}
|
||||
if len(result.TypeConflicts) > limit {
|
||||
lines = append(lines, fmt.Sprintf(" ... and %d more", len(result.TypeConflicts)-limit))
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user