mirror of
https://github.com/xataio/pgroll.git
synced 2024-09-11 05:45:48 +03:00
Implement 'rename column' migrations (#52)
Add support for **rename column** migrations. A rename column migration looks like: ```json { "name": "13_rename_column", "operations": [ { "rename_column": { "table": "employees", "from": "role", "to": "jobTitle" } } ] } ``` * On `Start`, the view in the new version schema aliases the renamed column to its new name. The column in the underlying table is not renamed. * `Rollback` is a no-op. * `Complete` renames the column in the underlying table.
This commit is contained in:
parent
9d6ad24fb1
commit
6448afe956
21
examples/12_create_employees_table.json
Normal file
21
examples/12_create_employees_table.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "12_create_employees_table",
|
||||
"operations": [
|
||||
{
|
||||
"create_table": {
|
||||
"name": "employees",
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"pk": true
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"type": "varchar(255)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
12
examples/13_rename_column.json
Normal file
12
examples/13_rename_column.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "13_rename_column",
|
||||
"operations": [
|
||||
{
|
||||
"rename_column": {
|
||||
"table": "employees",
|
||||
"from": "role",
|
||||
"to": "jobTitle"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -11,13 +11,14 @@ import (
|
||||
type OpName string
|
||||
|
||||
const (
|
||||
OpNameCreateTable OpName = "create_table"
|
||||
OpNameRenameTable OpName = "rename_table"
|
||||
OpNameDropTable OpName = "drop_table"
|
||||
OpNameAddColumn OpName = "add_column"
|
||||
OpNameDropColumn OpName = "drop_column"
|
||||
OpNameCreateIndex OpName = "create_index"
|
||||
OpNameDropIndex OpName = "drop_index"
|
||||
OpNameCreateTable OpName = "create_table"
|
||||
OpNameRenameTable OpName = "rename_table"
|
||||
OpNameDropTable OpName = "drop_table"
|
||||
OpNameAddColumn OpName = "add_column"
|
||||
OpNameDropColumn OpName = "drop_column"
|
||||
OpNameCreateIndex OpName = "create_index"
|
||||
OpNameDropIndex OpName = "drop_index"
|
||||
OpNameRenameColumn OpName = "rename_column"
|
||||
)
|
||||
|
||||
func TemporaryName(name string) string {
|
||||
@ -88,6 +89,9 @@ func (v *Operations) UnmarshalJSON(data []byte) error {
|
||||
case OpNameDropColumn:
|
||||
item = &OpDropColumn{}
|
||||
|
||||
case OpNameRenameColumn:
|
||||
item = &OpRenameColumn{}
|
||||
|
||||
case OpNameCreateIndex:
|
||||
item = &OpCreateIndex{}
|
||||
|
||||
@ -141,6 +145,9 @@ func (v Operations) MarshalJSON() ([]byte, error) {
|
||||
case *OpDropColumn:
|
||||
opName = OpNameDropColumn
|
||||
|
||||
case *OpRenameColumn:
|
||||
opName = OpNameRenameColumn
|
||||
|
||||
case *OpCreateIndex:
|
||||
opName = OpNameCreateIndex
|
||||
|
||||
|
56
pkg/migrations/op_rename_column.go
Normal file
56
pkg/migrations/op_rename_column.go
Normal file
@ -0,0 +1,56 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/lib/pq"
|
||||
|
||||
"pg-roll/pkg/schema"
|
||||
)
|
||||
|
||||
type OpRenameColumn struct {
|
||||
Table string `json:"table"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
var _ Operation = (*OpRenameColumn)(nil)
|
||||
|
||||
func (o *OpRenameColumn) Start(ctx context.Context, conn *sql.DB, schemaName string, stateSchema string, s *schema.Schema) error {
|
||||
table := s.GetTable(o.Table)
|
||||
table.RenameColumn(o.From, o.To)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpRenameColumn) Complete(ctx context.Context, conn *sql.DB) error {
|
||||
// rename the column in the underlying table
|
||||
_, err := conn.ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME COLUMN %s TO %s",
|
||||
pq.QuoteIdentifier(o.Table),
|
||||
pq.QuoteIdentifier(o.From),
|
||||
pq.QuoteIdentifier(o.To)))
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OpRenameColumn) Rollback(ctx context.Context, conn *sql.DB) error {
|
||||
// no-op
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpRenameColumn) Validate(ctx context.Context, s *schema.Schema) error {
|
||||
table := s.GetTable(o.Table)
|
||||
if table == nil {
|
||||
return TableDoesNotExistError{Name: o.Table}
|
||||
}
|
||||
|
||||
if table.GetColumn(o.From) == nil {
|
||||
return ColumnDoesNotExistError{Table: o.Table, Name: o.From}
|
||||
}
|
||||
|
||||
if table.GetColumn(o.To) != nil {
|
||||
return ColumnAlreadyExistsError{Table: o.Table, Name: o.From}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
81
pkg/migrations/op_rename_column_test.go
Normal file
81
pkg/migrations/op_rename_column_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package migrations_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"pg-roll/pkg/migrations"
|
||||
)
|
||||
|
||||
func TestRenameColumn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ExecuteTests(t, TestCases{{
|
||||
name: "rename column",
|
||||
migrations: []migrations.Migration{
|
||||
{
|
||||
Name: "01_add_table",
|
||||
Operations: migrations.Operations{
|
||||
&migrations.OpCreateTable{
|
||||
Name: "users",
|
||||
Columns: []migrations.Column{
|
||||
{
|
||||
Name: "id",
|
||||
Type: "serial",
|
||||
PrimaryKey: true,
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Type: "varchar(255)",
|
||||
Nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "02_rename_column",
|
||||
Operations: migrations.Operations{
|
||||
&migrations.OpRenameColumn{
|
||||
Table: "users",
|
||||
From: "username",
|
||||
To: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
afterStart: func(t *testing.T, db *sql.DB) {
|
||||
// The column in the underlying table has not been renamed.
|
||||
ColumnMustExist(t, db, "public", "users", "username")
|
||||
|
||||
// Insertions to the new column name in the new version schema should work.
|
||||
MustInsert(t, db, "public", "02_rename_column", "users", map[string]string{"name": "alice"})
|
||||
|
||||
// Insertions to the old column name in the old version schema should work.
|
||||
MustInsert(t, db, "public", "01_add_table", "users", map[string]string{"username": "bob"})
|
||||
|
||||
// Data can be read from the view in the new version schema.
|
||||
rows := MustSelect(t, db, "public", "02_rename_column", "users")
|
||||
assert.Equal(t, []map[string]any{
|
||||
{"id": 1, "name": "alice"},
|
||||
{"id": 2, "name": "bob"},
|
||||
}, rows)
|
||||
},
|
||||
afterRollback: func(t *testing.T, db *sql.DB) {
|
||||
// no-op
|
||||
},
|
||||
afterComplete: func(t *testing.T, db *sql.DB) {
|
||||
// The column in the underlying table has been renamed.
|
||||
ColumnMustExist(t, db, "public", "users", "name")
|
||||
|
||||
// Data can be read from the view in the new version schema.
|
||||
rows := MustSelect(t, db, "public", "02_rename_column", "users")
|
||||
assert.Equal(t, []map[string]any{
|
||||
{"id": 1, "name": "alice"},
|
||||
{"id": 2, "name": "bob"},
|
||||
}, rows)
|
||||
},
|
||||
}})
|
||||
}
|
@ -114,3 +114,8 @@ func (t *Table) AddColumn(name string, c Column) {
|
||||
func (t *Table) RemoveColumn(column string) {
|
||||
delete(t.Columns, column)
|
||||
}
|
||||
|
||||
func (t *Table) RenameColumn(from, to string) {
|
||||
t.Columns[to] = t.Columns[from]
|
||||
delete(t.Columns, from)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user