mirror of
https://github.com/xataio/pgroll.git
synced 2024-10-05 17:47:59 +03:00
Add rename_constraint
operation (#293)
This commit is contained in:
parent
def08e2bcc
commit
577870c325
@ -31,6 +31,7 @@
|
||||
* [Drop table](#drop-table)
|
||||
* [Raw SQL](#raw-sql)
|
||||
* [Rename table](#rename-table)
|
||||
* [Rename constraint](#rename-constraint)
|
||||
* [Set replica identity](#set-replica-identity)
|
||||
|
||||
## Concepts
|
||||
@ -686,6 +687,7 @@ See the [examples](../examples) directory for examples of each kind of operation
|
||||
* [Drop table](#drop-table)
|
||||
* [Raw SQL](#raw-sql)
|
||||
* [Rename table](#rename-table)
|
||||
* [Rename constraint](#rename-constraint)
|
||||
* [Set replica identity](#set-replica-identity)
|
||||
|
||||
### Add column
|
||||
@ -1112,6 +1114,28 @@ Example **rename table** migrations:
|
||||
|
||||
* [04_rename_table.json](../examples/04_rename_table.json)
|
||||
|
||||
|
||||
### Rename constraint
|
||||
|
||||
A rename constraint operation renames a constraint.
|
||||
|
||||
**rename constraint** operations have this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"rename_constraint": {
|
||||
"table": "table name",
|
||||
"from": "old constraint name",
|
||||
"to": "new constraint name"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example **rename constraint** migrations:
|
||||
|
||||
* [33_rename_constraint.json](../examples/33_rename_constraint.json)
|
||||
|
||||
|
||||
### Set replica identity
|
||||
|
||||
A set replica identity operation sets the replica identity for a table.
|
||||
|
12
examples/33_rename_check_constraint.json
Normal file
12
examples/33_rename_check_constraint.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "33_rename_check_constraint",
|
||||
"operations": [
|
||||
{
|
||||
"rename_constraint": {
|
||||
"table": "people",
|
||||
"from": "name_length",
|
||||
"to": "name_length_check"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -137,6 +137,15 @@ func (e ConstraintDoesNotExistError) Error() string {
|
||||
return fmt.Sprintf("constraint %q on table %q does not exist", e.Constraint, e.Table)
|
||||
}
|
||||
|
||||
type ConstraintAlreadyExistsError struct {
|
||||
Table string
|
||||
Constraint string
|
||||
}
|
||||
|
||||
func (e ConstraintAlreadyExistsError) Error() string {
|
||||
return fmt.Sprintf("constraint %q on table %q already exists", e.Constraint, e.Table)
|
||||
}
|
||||
|
||||
type NoUpSQLAllowedError struct{}
|
||||
|
||||
func (e NoUpSQLAllowedError) Error() string {
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
OpNameAlterColumn OpName = "alter_column"
|
||||
OpNameCreateIndex OpName = "create_index"
|
||||
OpNameDropIndex OpName = "drop_index"
|
||||
OpNameRenameConstraint OpName = "rename_constraint"
|
||||
OpNameDropConstraint OpName = "drop_constraint"
|
||||
OpNameSetReplicaIdentity OpName = "set_replica_identity"
|
||||
OpRawSQLName OpName = "sql"
|
||||
@ -95,6 +96,9 @@ func (v *Operations) UnmarshalJSON(data []byte) error {
|
||||
case OpNameDropColumn:
|
||||
item = &OpDropColumn{}
|
||||
|
||||
case OpNameRenameConstraint:
|
||||
item = &OpRenameConstraint{}
|
||||
|
||||
case OpNameDropConstraint:
|
||||
item = &OpDropConstraint{}
|
||||
|
||||
@ -175,6 +179,9 @@ func OperationName(op Operation) OpName {
|
||||
case *OpDropColumn:
|
||||
return OpNameDropColumn
|
||||
|
||||
case *OpRenameConstraint:
|
||||
return OpNameRenameConstraint
|
||||
|
||||
case *OpDropConstraint:
|
||||
return OpNameDropConstraint
|
||||
|
||||
|
47
pkg/migrations/op_rename_constraint.go
Normal file
47
pkg/migrations/op_rename_constraint.go
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/xataio/pgroll/pkg/schema"
|
||||
)
|
||||
|
||||
var _ Operation = (*OpRenameConstraint)(nil)
|
||||
|
||||
func (o *OpRenameConstraint) Start(ctx context.Context, conn *sql.DB, stateSchema string, s *schema.Schema, cbs ...CallbackFn) (*schema.Table, error) {
|
||||
// no-op
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *OpRenameConstraint) Complete(ctx context.Context, conn *sql.DB, s *schema.Schema) error {
|
||||
// rename the constraint in the underlying table
|
||||
_, err := conn.ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME CONSTRAINT %s TO %s",
|
||||
pq.QuoteIdentifier(o.Table),
|
||||
pq.QuoteIdentifier(o.From),
|
||||
pq.QuoteIdentifier(o.To)))
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OpRenameConstraint) Rollback(ctx context.Context, conn *sql.DB) error {
|
||||
// no-op
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpRenameConstraint) Validate(ctx context.Context, s *schema.Schema) error {
|
||||
table := s.GetTable(o.Table)
|
||||
|
||||
if !table.ConstraintExists(o.From) {
|
||||
return ConstraintDoesNotExistError{Table: o.Table, Constraint: o.From}
|
||||
}
|
||||
|
||||
if table.ConstraintExists(o.To) {
|
||||
return ConstraintAlreadyExistsError{Table: o.Table, Constraint: o.To}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
102
pkg/migrations/op_rename_constraint_test.go
Normal file
102
pkg/migrations/op_rename_constraint_test.go
Normal file
@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrations_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xataio/pgroll/pkg/migrations"
|
||||
)
|
||||
|
||||
func TestRenameConstraint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
addTableMigration := migrations.Migration{
|
||||
Name: "01_add_table",
|
||||
Operations: migrations.Operations{
|
||||
&migrations.OpCreateTable{
|
||||
Name: "users",
|
||||
Columns: []migrations.Column{
|
||||
{
|
||||
Name: "id",
|
||||
Type: "serial",
|
||||
Pk: ptr(true),
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Type: "text",
|
||||
Nullable: ptr(false),
|
||||
Check: &migrations.CheckConstraint{Constraint: `LENGTH("username") <= 2048`, Name: "users_text_length_username"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ExecuteTests(t, TestCases{{
|
||||
name: "rename constraint",
|
||||
migrations: []migrations.Migration{
|
||||
addTableMigration,
|
||||
{
|
||||
Name: "02_rename_constraint",
|
||||
Operations: migrations.Operations{
|
||||
&migrations.OpAlterColumn{
|
||||
Table: "users",
|
||||
Column: "username",
|
||||
Name: ptr("name"),
|
||||
},
|
||||
&migrations.OpRenameConstraint{
|
||||
Table: "users",
|
||||
From: "users_text_length_username",
|
||||
To: "users_text_length_name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
afterStart: func(t *testing.T, db *sql.DB, schema string) {
|
||||
// The column in the underlying table has not been renamed.
|
||||
ColumnMustExist(t, db, schema, "users", "username")
|
||||
|
||||
// Insertions to the new column name in the new version schema should work.
|
||||
MustInsert(t, db, schema, "02_rename_constraint", "users", map[string]string{"name": "alice"})
|
||||
|
||||
// Insertions to the old column name in the old version schema should work.
|
||||
MustInsert(t, db, schema, "01_add_table", "users", map[string]string{"username": "bob"})
|
||||
|
||||
// The check constraint in the underlying table has not been renamed.
|
||||
CheckConstraintMustExist(t, db, schema, "users", "users_text_length_username")
|
||||
|
||||
// The new check constraint in the underlying table has not been created.
|
||||
CheckConstraintMustNotExist(t, db, schema, "users", "users_text_length_name")
|
||||
|
||||
// Data can be read from the view in the new version schema.
|
||||
rows := MustSelect(t, db, schema, "02_rename_constraint", "users")
|
||||
assert.Equal(t, []map[string]any{
|
||||
{"id": 1, "name": "alice"},
|
||||
{"id": 2, "name": "bob"},
|
||||
}, rows)
|
||||
},
|
||||
afterRollback: func(t *testing.T, db *sql.DB, schema string) {
|
||||
// no-op
|
||||
},
|
||||
afterComplete: func(t *testing.T, db *sql.DB, schema string) {
|
||||
// The column in the underlying table has been renamed.
|
||||
ColumnMustExist(t, db, schema, "users", "name")
|
||||
|
||||
// The check constraint in the underlying table has been renamed.
|
||||
CheckConstraintMustExist(t, db, schema, "users", "users_text_length_name")
|
||||
|
||||
// The old check constraint in the underlying table has been dropped.
|
||||
CheckConstraintMustNotExist(t, db, schema, "users", "users_text_length_username")
|
||||
|
||||
// Data can be read from the view in the new version schema.
|
||||
rows := MustSelect(t, db, schema, "02_rename_constraint", "users")
|
||||
assert.Equal(t, []map[string]any{
|
||||
{"id": 1, "name": "alice"},
|
||||
{"id": 2, "name": "bob"},
|
||||
}, rows)
|
||||
},
|
||||
}})
|
||||
}
|
@ -189,6 +189,18 @@ type OpRawSQL struct {
|
||||
Up string `json:"up"`
|
||||
}
|
||||
|
||||
// Rename constraint operation
|
||||
type OpRenameConstraint struct {
|
||||
// Name of the constraint
|
||||
From string `json:"from"`
|
||||
|
||||
// Name of the table
|
||||
Table string `json:"table"`
|
||||
|
||||
// New name of the constraint
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
// Rename table operation
|
||||
type OpRenameTable struct {
|
||||
// Old name of the table
|
||||
|
20
schema.json
20
schema.json
@ -348,6 +348,26 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"OpRenameConstraint": {
|
||||
"additionalProperties": false,
|
||||
"description": "Rename constraint operation",
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "Name of the constraint",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "New name of the constraint",
|
||||
"type": "string"
|
||||
},
|
||||
"table": {
|
||||
"description": "Name of the table",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["from", "to", "table"],
|
||||
"type": "object"
|
||||
},
|
||||
"OpRenameTable": {
|
||||
"additionalProperties": false,
|
||||
"description": "Rename table operation",
|
||||
|
Loading…
Reference in New Issue
Block a user