From 4d01a56b8de77ba4420bb508a0685145cf591147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Y=C4=B1lmaz?= Date: Mon, 20 Dec 2021 12:29:07 +0300 Subject: [PATCH] dialect/sql/schema: allow adding DEFAULT to columns on migration #1758 (#2199) * add feature to SET DEFAULT value when add new default value to a existing column * refactoring unit tests * correct misspells * code review refactors * refactors * Update dialect/sql/schema/postgres.go * Update dialect/sql/schema/postgres.go * Update dialect/sql/schema/postgres.go * Update dialect/sql/schema/postgres.go * Update dialect/sql/schema/postgres.go Co-authored-by: Ariel Mashraki <7413593+a8m@users.noreply.github.com> --- dialect/sql/schema/migrate.go | 3 +++ dialect/sql/schema/mysql_test.go | 2 +- dialect/sql/schema/postgres.go | 14 ++++++++--- dialect/sql/schema/postgres_test.go | 38 ++++++++++++++++++++++++++--- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/dialect/sql/schema/migrate.go b/dialect/sql/schema/migrate.go index 1a8c9f8a7..36a39bfae 100644 --- a/dialect/sql/schema/migrate.go +++ b/dialect/sql/schema/migrate.go @@ -375,6 +375,9 @@ func (m *Migrate) changeSet(curr, new *Table) (*changes, error) { // Change nullability of a column. case c1.Nullable != c2.Nullable: change.column.modify = append(change.column.modify, c1) + // Change default value. + case c1.Default != nil && c2.Default == nil: + change.column.modify = append(change.column.modify, c1) } } diff --git a/dialect/sql/schema/mysql_test.go b/dialect/sql/schema/mysql_test.go index e41c5c23f..f8d1a8d87 100644 --- a/dialect/sql/schema/mysql_test.go +++ b/dialect/sql/schema/mysql_test.go @@ -301,7 +301,7 @@ func TestMySQL_Create(t *testing.T) { WithArgs("users"). WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "sub_part", "non_unique", "seq_in_index"}). AddRow("PRIMARY", "id", nil, "0", "1")) - mock.ExpectExec(escape("ALTER TABLE `users` ADD COLUMN `age` bigint NOT NULL, ADD COLUMN `ts` timestamp NOT NULL")). + mock.ExpectExec(escape("ALTER TABLE `users` ADD COLUMN `age` bigint NOT NULL, ADD COLUMN `ts` timestamp NOT NULL, MODIFY COLUMN `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP")). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectCommit() }, diff --git a/dialect/sql/schema/postgres.go b/dialect/sql/schema/postgres.go index 34d8f3b0f..7783773cd 100644 --- a/dialect/sql/schema/postgres.go +++ b/dialect/sql/schema/postgres.go @@ -380,7 +380,7 @@ func (d *Postgres) addColumn(c *Column) *sql.ColumnBuilder { b.Attr("GENERATED BY DEFAULT AS IDENTITY") } c.nullable(b) - d.writeDefault(b, c) + d.writeDefault(b, c, "DEFAULT") if c.Collation != "" { b.Attr("COLLATE " + strconv.Quote(c.Collation)) } @@ -389,7 +389,7 @@ func (d *Postgres) addColumn(c *Column) *sql.ColumnBuilder { // writeDefault writes the `DEFAULT` clause to column builder // if exists and supported by the driver. -func (d *Postgres) writeDefault(b *sql.ColumnBuilder, c *Column) { +func (d *Postgres) writeDefault(b *sql.ColumnBuilder, c *Column, clause string) { if c.Default == nil || !c.supportDefault() { return } @@ -403,7 +403,7 @@ func (d *Postgres) writeDefault(b *sql.ColumnBuilder, c *Column) { attr = fmt.Sprintf("'%s'", strings.ReplaceAll(v, "'", "''")) } } - b.Attr("DEFAULT " + attr) + b.Attr(clause + " " + attr) } // alterColumn returns list of ColumnBuilder for applying in order to alter a column. @@ -415,9 +415,17 @@ func (d *Postgres) alterColumn(c *Column) (ops []*sql.ColumnBuilder) { } else { ops = append(ops, b.Column(c.Name).Attr("SET NOT NULL")) } + if c.Default != nil && c.supportDefault() { + ops = append(ops, d.writeSetDefault(b.Column(c.Name), c)) + } return ops } +func (d *Postgres) writeSetDefault(b *sql.ColumnBuilder, c *Column) *sql.ColumnBuilder { + d.writeDefault(b, c, "SET DEFAULT") + return b +} + // hasUniqueName reports if the index has a unique name in the schema. func hasUniqueName(i *Index) bool { // Trim the "_key" suffix if it was added by Postgres for implicit indexes. diff --git a/dialect/sql/schema/postgres_test.go b/dialect/sql/schema/postgres_test.go index a04b238dc..73444b51b 100644 --- a/dialect/sql/schema/postgres_test.go +++ b/dialect/sql/schema/postgres_test.go @@ -211,6 +211,8 @@ func TestPostgres_Create(t *testing.T) { mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "CURRENT_SCHEMA()", "users"))). WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique", "seq_in_index"}). AddRow("users_pkey", "id", "t", "t", 0)) + mock.ExpectExec(escape(`ALTER TABLE "users" ALTER COLUMN "block_size" TYPE bigint, ALTER COLUMN "block_size" SET NOT NULL, ALTER COLUMN "block_size" SET DEFAULT current_setting('block_size')::bigint`)). + WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectCommit() }, }, @@ -288,7 +290,7 @@ func TestPostgres_Create(t *testing.T) { mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "CURRENT_SCHEMA()", "users"))). WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique", "seq_in_index"}). AddRow("users_pkey", "id", "t", "t", 0)) - mock.ExpectExec(escape(`ALTER TABLE "users" ADD COLUMN "age" bigint NOT NULL, ALTER COLUMN "updated_at" TYPE timestamp with time zone, ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "deleted_at" TYPE timestamp with time zone, ALTER COLUMN "deleted_at" DROP NOT NULL`)). + mock.ExpectExec(escape(`ALTER TABLE "users" ADD COLUMN "age" bigint NOT NULL, ALTER COLUMN "created_at" TYPE date, ALTER COLUMN "created_at" SET NOT NULL, ALTER COLUMN "created_at" SET DEFAULT CURRENT_DATE, ALTER COLUMN "updated_at" TYPE timestamp with time zone, ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "deleted_at" TYPE timestamp with time zone, ALTER COLUMN "deleted_at" DROP NOT NULL`)). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectCommit() }, @@ -513,6 +515,36 @@ func TestPostgres_Create(t *testing.T) { mock.ExpectCommit() }, }, + { + name: "modify column default value", + tables: []*Table{ + { + Name: "users", + Columns: []*Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "name", Type: field.TypeString, Default: "unknown"}, + }, + PrimaryKey: []*Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + }, + }, + }, + before: func(mock pgMock) { + mock.start("120000") + mock.tableExists("users", true) + mock.ExpectQuery(escape(`SELECT "column_name", "data_type", "is_nullable", "column_default", "udt_name", "numeric_precision", "numeric_scale", "character_maximum_length" FROM "information_schema"."columns" WHERE "table_schema" = CURRENT_SCHEMA() AND "table_name" = $1`)). + WithArgs("users"). + WillReturnRows(sqlmock.NewRows([]string{"column_name", "data_type", "is_nullable", "column_default", "udt_name", "numeric_precision", "numeric_scale", "character_maximum_length"}). + AddRow("id", "bigint", "NO", "NULL", "int8", nil, nil, nil). + AddRow("name", "character", "NO", "NULL", "bpchar", nil, nil, nil)) + mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "CURRENT_SCHEMA()", "users"))). + WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique", "seq_in_index"}). + AddRow("users_pkey", "id", "t", "t", 0)) + mock.ExpectExec(escape(`ALTER TABLE "users" ALTER COLUMN "name" TYPE varchar, ALTER COLUMN "name" SET NOT NULL, ALTER COLUMN "name" SET DEFAULT 'unknown'`)). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + }, + }, { name: "apply uniqueness on column", tables: []*Table{ @@ -923,7 +955,7 @@ func TestPostgres_Create(t *testing.T) { Name: "users", Columns: []*Column{ {Name: "id", Type: field.TypeInt, Increment: true}, - {Name: "name", Type: field.TypeString, Nullable: false, SchemaType: map[string]string{dialect.Postgres: "varchar(20)"}}, + {Name: "name", Type: field.TypeString, SchemaType: map[string]string{dialect.Postgres: "varchar(20)"}, Default: "unknown"}, }, PrimaryKey: []*Column{ {Name: "id", Type: field.TypeInt, Increment: true}, @@ -941,7 +973,7 @@ func TestPostgres_Create(t *testing.T) { mock.ExpectQuery(escape(fmt.Sprintf(indexesQuery, "CURRENT_SCHEMA()", "users"))). WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "primary", "unique", "seq_in_index"}). AddRow("users_pkey", "id", "t", "t", 0)) - mock.ExpectExec(escape(`ALTER TABLE "users" ALTER COLUMN "name" TYPE varchar(20), ALTER COLUMN "name" SET NOT NULL`)). + mock.ExpectExec(escape(`ALTER TABLE "users" ALTER COLUMN "name" TYPE varchar(20), ALTER COLUMN "name" SET NOT NULL, ALTER COLUMN "name" SET DEFAULT 'unknown'`)). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectCommit() },