Repository: ariga/atlas (go)

Issue:
 replacing or reordering enum ("ENUM NAME") value is not supported:
I have added some values to the Enum in Postgress , now, I want to migrate that, but I got this error:

`Error: replacing or reordering enum ("t_job_application_process_status") value is not supported: ["JOB_RECOMMENDED" "JOB_MATCHING" "JOB_MATCHED" "JOB_NOT_MATCHED" "PENDING_APPLY" "APPLYING" "APPLIED" "APPLY_FAILED"] != ["PENDING_APPLY" "APPLYING" "APPLIED" "APPLY_FAILED"]`

is there any way to fix it? 

![image](https://github.com/ariga/atlas/assets/136852757/52686329-280a-4497-8530-81873af5a9b1)


Pull request description:
sql/postgres: support add enum values before/after exisiting values
Fixed #2252

Patch:
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index 2304ef56..10272dd3 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -824,20 +824,41 @@ func (s *state) alterEnum(modify *schema.ModifyObject) error {
 	if !ok1 || !ok2 {
 		return fmt.Errorf("altering objects (%T) to (%T) is not supported", modify.From, modify.To)
 	}
-	if len(from.Values) > len(to.Values) {
-		return fmt.Errorf("dropping enum (%q) value is not supported", from.T)
+	fromV := make(map[string]int, len(from.Values))
+	for i, v := range from.Values {
+		fromV[v] = i
 	}
-	for i := range from.Values {
-		if from.Values[i] != to.Values[i] {
-			return fmt.Errorf("replacing or reordering enum (%q) value is not supported: %q != %q", to.T, to.Values, from.Values)
+	toV := make(map[string]int, len(to.Values))
+	for i, v := range from.Values {
+		toV[v] = i
+	}
+	for v := range fromV {
+		if _, ok := toV[v]; !ok {
+			return fmt.Errorf("dropping value %q from enum %q is not supported", v, from.T)
 		}
 	}
-	name := s.enumIdent(from)
-	for _, v := range to.Values[len(from.Values):] {
-		s.append(&migrate.Change{
-			Cmd:     s.Build("ALTER TYPE").P(name, "ADD VALUE", quote(v)).String(),
-			Comment: fmt.Sprintf("add value to enum type: %q", from.T),
-		})
+	var (
+		at   int
+		name = s.enumIdent(from)
+	)
+	for i, v := range to.Values {
+		b := s.Build("ALTER TYPE").P(name, "ADD VALUE", quote(v))
+		switch j, ok := fromV[v]; {
+		case !ok:
+			if i == 0 && len(from.Values) > 0 {
+				b.P("BEFORE").P(quote(from.Values[0]))
+			} else if i > 0 && at != len(from.Values) {
+				b.P("AFTER").P(quote(to.Values[i-1]))
+			}
+			s.append(&migrate.Change{
+				Cmd:     b.String(),
+				Comment: fmt.Sprintf("add value to enum type: %q", from.T),
+			})
+		case ok && j == at:
+			at++
+		default:
+			return fmt.Errorf("reordering enum %q value %q is not supported", from.T, v)
+		}
 	}
 	return nil
 }


Test patch:
diff --git a/sql/postgres/migrate_test.go b/sql/postgres/migrate_test.go
index eff59c10..da71c206 100644
--- a/sql/postgres/migrate_test.go
+++ b/sql/postgres/migrate_test.go
@@ -974,6 +974,7 @@ func TestPlanChanges(t *testing.T) {
 				},
 			},
 		},
+		// Append enum value at the end.
 		{
 			changes: []schema.Change{
 				&schema.ModifyObject{
@@ -997,6 +998,93 @@ func TestPlanChanges(t *testing.T) {
 				},
 			},
 		},
+		// Append enum values at the beginning.
+		{
+			changes: []schema.Change{
+				&schema.ModifyObject{
+					From: &schema.EnumType{
+						T:      "state",
+						Values: []string{"on", "off"},
+						Schema: schema.New("public"),
+					},
+					To: &schema.EnumType{
+						T:      "state",
+						Values: []string{"unknown", "null", "on", "off"},
+						Schema: schema.New("public"),
+					},
+				},
+			},
+			wantPlan: &migrate.Plan{
+				Reversible:    false,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{Cmd: `ALTER TYPE "public"."state" ADD VALUE 'unknown' BEFORE 'on'`},
+					{Cmd: `ALTER TYPE "public"."state" ADD VALUE 'null' AFTER 'unknown'`},
+				},
+			},
+		},
+		// Append values at beginning, middle and end.
+		{
+			changes: []schema.Change{
+				&schema.ModifyObject{
+					From: &schema.EnumType{
+						T:      "state",
+						Values: []string{"on", "off", "on-off"},
+						Schema: schema.New("public"),
+					},
+					To: &schema.EnumType{
+						T:      "state",
+						Values: []string{"start", "on", "null", "off", "unknown", "on-off"},
+						Schema: schema.New("public"),
+					},
+				},
+			},
+			wantPlan: &migrate.Plan{
+				Reversible:    false,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{Cmd: `ALTER TYPE "public"."state" ADD VALUE 'start' BEFORE 'on'`},
+					{Cmd: `ALTER TYPE "public"."state" ADD VALUE 'null' AFTER 'on'`},
+					{Cmd: `ALTER TYPE "public"."state" ADD VALUE 'unknown' AFTER 'off'`},
+				},
+			},
+		},
+		// Enum reordering.
+		{
+			changes: []schema.Change{
+				&schema.ModifyObject{
+					From: &schema.EnumType{
+						T:      "state",
+						Values: []string{"on", "off"},
+						Schema: schema.New("public"),
+					},
+					To: &schema.EnumType{
+						T:      "state",
+						Values: []string{"off", "on"},
+						Schema: schema.New("public"),
+					},
+				},
+			},
+			wantErr: true,
+		},
+		// Enum value dropping.
+		{
+			changes: []schema.Change{
+				&schema.ModifyObject{
+					From: &schema.EnumType{
+						T:      "state",
+						Values: []string{"on", "off"},
+						Schema: schema.New("public"),
+					},
+					To: &schema.EnumType{
+						T:      "state",
+						Values: []string{"off"},
+						Schema: schema.New("public"),
+					},
+				},
+			},
+			wantErr: true,
+		},
 		// Modify column type and drop comment.
 		{
 			changes: []schema.Change{