Move set_not_null operation into alter_column (#93)

Build on #91 and #92 and move the `set_not_null` operation into the
`alter_column` operation.

The pattern is the same as for the operations already moved:

* Update the example migration to use the new operation type
* Remove serialization and deserialization logic for the individual
operation.
* Make the `alter_column` operation construct the right type of 'inner
operation'.
* Update tests for the foreign key and check constraint ops to use the
`alter_column` operation.

A migration to set a column `NOT NULL` now looks like:

```json
{
  "name": "16_set_not_null",
  "operations": [
    {
      "alter_column": {
        "table": "reviews",
        "column": "review",
        "not_null": true,
        "up": "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
        "down": "review"
      }
    }
  ]
}
```

The `not_null` field is currently only allowed to be set `true`, as
removing this kind of constraint is currently unsupported.
This commit is contained in:
Andrew Farries 2023-09-14 05:47:16 +01:00 committed by GitHub
parent 6930e506c9
commit fac65ab092
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 36 deletions

View File

@ -2,9 +2,10 @@
"name": "16_set_not_null",
"operations": [
{
"set_not_null": {
"alter_column": {
"table": "reviews",
"column": "review",
"not_null": true,
"up": "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
"down": "review"
}

View File

@ -3,6 +3,7 @@ package migrations
import (
"context"
"database/sql"
"fmt"
"github.com/xataio/pg-roll/pkg/schema"
)
@ -14,6 +15,7 @@ type OpAlterColumn struct {
Type string `json:"type"`
Check string `json:"check"`
References *ColumnReference `json:"references"`
NotNull *bool `json:"not_null"`
Up string `json:"up"`
Down string `json:"down"`
}
@ -45,13 +47,19 @@ func (o *OpAlterColumn) Validate(ctx context.Context, s *schema.Schema) error {
return MultipleAlterColumnChangesError{}
}
if _, ok := op.(*OpRenameColumn); ok {
switch op.(type) {
case *OpRenameColumn:
if o.Up != "" {
return NoUpSQLAllowedError{}
}
if o.Down != "" {
return NoDownSQLAllowedError{}
}
case *OpSetNotNull:
if o.NotNull != nil && !*o.NotNull {
return fmt.Errorf("removing NOT NULL constraints is not supported")
}
}
return op.Validate(ctx, s)
@ -92,6 +100,14 @@ func (o *OpAlterColumn) innerOperation() Operation {
Up: o.Up,
Down: o.Down,
}
case o.NotNull != nil:
return &OpSetNotNull{
Table: o.Table,
Column: o.Column,
Up: o.Up,
Down: o.Down,
}
}
return nil
}
@ -114,6 +130,9 @@ func (o *OpAlterColumn) oneChange() bool {
if o.References != nil {
fieldsSet++
}
if o.NotNull != nil {
fieldsSet++
}
return fieldsSet == 1
}

View File

@ -110,9 +110,6 @@ func (v *Operations) UnmarshalJSON(data []byte) error {
case OpNameSetUnique:
item = &OpSetUnique{}
case OpNameSetNotNull:
item = &OpSetNotNull{}
case OpRawSQLName:
item = &OpRawSQL{}
@ -187,9 +184,6 @@ func OperationName(op Operation) OpName {
case *OpSetUnique:
return OpNameSetUnique
case *OpSetNotNull:
return OpNameSetNotNull
case *OpRawSQL:
return OpRawSQLName

View File

@ -48,10 +48,11 @@ func TestSetNotNull(t *testing.T) {
{
Name: "02_set_not_null",
Operations: migrations.Operations{
&migrations.OpSetNotNull{
Table: "reviews",
Column: "review",
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
&migrations.OpAlterColumn{
Table: "reviews",
Column: "review",
NotNull: ptr(true),
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
},
},
},
@ -189,11 +190,12 @@ func TestSetNotNull(t *testing.T) {
{
Name: "02_set_not_null",
Operations: migrations.Operations{
&migrations.OpSetNotNull{
Table: "reviews",
Column: "review",
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review || ' (from new column)'",
&migrations.OpAlterColumn{
Table: "reviews",
Column: "review",
NotNull: ptr(true),
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review || ' (from new column)'",
},
},
},
@ -263,10 +265,11 @@ func TestSetNotNullValidation(t *testing.T) {
{
Name: "02_set_not_null",
Operations: migrations.Operations{
&migrations.OpSetNotNull{
Table: "reviews",
Column: "review",
Down: "review",
&migrations.OpAlterColumn{
Table: "reviews",
Column: "review",
NotNull: ptr(true),
Down: "review",
},
},
},
@ -280,11 +283,12 @@ func TestSetNotNullValidation(t *testing.T) {
{
Name: "02_set_not_null",
Operations: migrations.Operations{
&migrations.OpSetNotNull{
Table: "doesntexist",
Column: "review",
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review",
&migrations.OpAlterColumn{
Table: "doesntexist",
Column: "review",
NotNull: ptr(true),
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review",
},
},
},
@ -298,11 +302,12 @@ func TestSetNotNullValidation(t *testing.T) {
{
Name: "02_set_not_null",
Operations: migrations.Operations{
&migrations.OpSetNotNull{
Table: "reviews",
Column: "doesntexist",
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review",
&migrations.OpAlterColumn{
Table: "reviews",
Column: "doesntexist",
NotNull: ptr(true),
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review",
},
},
},
@ -345,11 +350,12 @@ func TestSetNotNullValidation(t *testing.T) {
{
Name: "02_set_not_null",
Operations: migrations.Operations{
&migrations.OpSetNotNull{
Table: "reviews",
Column: "review",
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review",
&migrations.OpAlterColumn{
Table: "reviews",
Column: "review",
NotNull: ptr(true),
Up: "(SELECT CASE WHEN review IS NULL THEN product || ' is good' ELSE review END)",
Down: "review",
},
},
},