Skip to content

Commit

Permalink
Add rename_constraint operation (#293)
Browse files Browse the repository at this point in the history
  • Loading branch information
SferaDev committed Mar 1, 2024
1 parent def08e2 commit 577870c
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 0 deletions.
24 changes: 24 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions examples/33_rename_check_constraint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "33_rename_check_constraint",
"operations": [
{
"rename_constraint": {
"table": "people",
"from": "name_length",
"to": "name_length_check"
}
}
]
}
9 changes: 9 additions & 0 deletions pkg/migrations/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions pkg/migrations/op_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -95,6 +96,9 @@ func (v *Operations) UnmarshalJSON(data []byte) error {
case OpNameDropColumn:
item = &OpDropColumn{}

case OpNameRenameConstraint:
item = &OpRenameConstraint{}

case OpNameDropConstraint:
item = &OpDropConstraint{}

Expand Down Expand Up @@ -175,6 +179,9 @@ func OperationName(op Operation) OpName {
case *OpDropColumn:
return OpNameDropColumn

case *OpRenameConstraint:
return OpNameRenameConstraint

case *OpDropConstraint:
return OpNameDropConstraint

Expand Down
47 changes: 47 additions & 0 deletions pkg/migrations/op_rename_constraint.go
Original file line number Diff line number Diff line change
@@ -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 changes: 102 additions & 0 deletions pkg/migrations/op_rename_constraint_test.go
Original file line number Diff line number Diff line change
@@ -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)
},
}})
}
12 changes: 12 additions & 0 deletions pkg/migrations/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 577870c

Please sign in to comment.