Implement 'drop index' migrations (#51)

Add support for **drop index** migrations. A drop index migration looks
like this:

```json
{
  "name": "11_drop_index",
  "operations": [
    {
      "drop_index": {
        "name": "_pgroll_idx_users_name"
      }
    }
  ]
}
```

* `Start` is a no-op.
* On `Complete` the index is removed from the underlying table.
* `Rollback` is a no-op.
This commit is contained in:
Andrew Farries 2023-08-18 06:38:48 +01:00 committed by GitHub
parent a44b475f0b
commit 9d6ad24fb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 135 additions and 0 deletions

View File

@ -0,0 +1,10 @@
{
"name": "11_drop_index",
"operations": [
{
"drop_index": {
"name": "_pgroll_idx_fruits_name"
}
}
]
}

View File

@ -43,3 +43,11 @@ type IndexAlreadyExistsError struct {
func (e IndexAlreadyExistsError) Error() string {
return fmt.Sprintf("index %q already exists", e.Name)
}
type IndexDoesNotExistError struct {
Name string
}
func (e IndexDoesNotExistError) Error() string {
return fmt.Sprintf("index %q does not exist", e.Name)
}

View File

@ -17,6 +17,7 @@ const (
OpNameAddColumn OpName = "add_column"
OpNameDropColumn OpName = "drop_column"
OpNameCreateIndex OpName = "create_index"
OpNameDropIndex OpName = "drop_index"
)
func TemporaryName(name string) string {
@ -90,6 +91,9 @@ func (v *Operations) UnmarshalJSON(data []byte) error {
case OpNameCreateIndex:
item = &OpCreateIndex{}
case OpNameDropIndex:
item = &OpDropIndex{}
default:
return fmt.Errorf("unknown migration type: %v", opName)
}
@ -140,6 +144,9 @@ func (v Operations) MarshalJSON() ([]byte, error) {
case *OpCreateIndex:
opName = OpNameCreateIndex
case *OpDropIndex:
opName = OpNameDropIndex
default:
panic(fmt.Errorf("unknown operation for %T", op))
}

View File

@ -0,0 +1,42 @@
package migrations
import (
"context"
"database/sql"
"fmt"
"pg-roll/pkg/schema"
)
type OpDropIndex struct {
Name string `json:"name"`
}
var _ Operation = (*OpDropIndex)(nil)
func (o *OpDropIndex) Start(ctx context.Context, conn *sql.DB, schemaName string, stateSchema string, s *schema.Schema) error {
// no-op
return nil
}
func (o *OpDropIndex) Complete(ctx context.Context, conn *sql.DB) error {
// drop the index concurrently
_, err := conn.ExecContext(ctx, fmt.Sprintf("DROP INDEX CONCURRENTLY IF EXISTS %s", o.Name))
return err
}
func (o *OpDropIndex) Rollback(ctx context.Context, conn *sql.DB) error {
// no-op
return nil
}
func (o *OpDropIndex) Validate(ctx context.Context, s *schema.Schema) error {
for _, table := range s.Tables {
_, ok := table.Indexes[o.Name]
if ok {
return nil
}
}
return IndexDoesNotExistError{Name: o.Name}
}

View File

@ -0,0 +1,68 @@
package migrations_test
import (
"database/sql"
"testing"
"pg-roll/pkg/migrations"
)
func TestDropIndex(t *testing.T) {
t.Parallel()
idxName := migrations.GenerateIndexName("users", []string{"name"})
ExecuteTests(t, TestCases{{
name: "drop index",
migrations: []migrations.Migration{
{
Name: "01_add_table",
Operations: migrations.Operations{
&migrations.OpCreateTable{
Name: "users",
Columns: []migrations.Column{
{
Name: "id",
Type: "serial",
PrimaryKey: true,
},
{
Name: "name",
Type: "varchar(255)",
Nullable: false,
},
},
},
},
},
{
Name: "02_create_index",
Operations: migrations.Operations{
&migrations.OpCreateIndex{
Table: "users",
Columns: []string{"name"},
},
},
},
{
Name: "03_drop_index",
Operations: migrations.Operations{
&migrations.OpDropIndex{
Name: idxName,
},
},
},
},
afterStart: func(t *testing.T, db *sql.DB) {
// The index has not yet been dropped.
IndexMustExist(t, db, "public", "users", idxName)
},
afterRollback: func(t *testing.T, db *sql.DB) {
// Rollback is a no-op.
},
afterComplete: func(t *testing.T, db *sql.DB) {
// The index has been removed from the underlying table.
IndexMustNotExist(t, db, "public", "users", idxName)
},
}})
}