diff --git a/.gitignore b/.gitignore index 0e471ec..9b01109 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ coverage.txt dist .release-env -unit_coverage.out \ No newline at end of file +unit_coverage.out +.mgr8 \ No newline at end of file diff --git a/domain/column_diff.go b/domain/column_diff.go index 2d79afe..1bdbd06 100644 --- a/domain/column_diff.go +++ b/domain/column_diff.go @@ -71,3 +71,22 @@ func (m *MakeColumnNullableDiff) Up(deparser Deparser) string { func (m *MakeColumnNullableDiff) Down(deparser Deparser) string { return deparser.MakeColumnNotNull(m.tableName, m.columnName, m.column) } + +type ChangeColumnDataTypeParameterDiff struct { + tableName string + columnName string + originalColumn *Column + column *Column +} + +func NewChangeColumnParameterDiff(tableName string, columnName string, originalColumn *Column, column *Column) *ChangeColumnDataTypeParameterDiff { + return &ChangeColumnDataTypeParameterDiff{tableName: tableName, columnName: columnName, originalColumn: originalColumn, column: column} +} + +func (m *ChangeColumnDataTypeParameterDiff) Up(deparser Deparser) string { + return deparser.ChangeDataTypeParameters(m.tableName, m.columnName, m.column) +} + +func (m *ChangeColumnDataTypeParameterDiff) Down(deparser Deparser) string { + return deparser.ChangeDataTypeParameters(m.tableName, m.columnName, m.originalColumn) +} diff --git a/domain/diff.go b/domain/diff.go index 9cbd131..e2a7b3c 100644 --- a/domain/diff.go +++ b/domain/diff.go @@ -1,5 +1,7 @@ package domain +import "reflect" + type Diff interface { Up(driver Deparser) string Down(driver Deparser) string @@ -50,6 +52,11 @@ func (t *Table) Diff(originalTable *Table) DiffDeque { func (c *Column) Diff(table *Table, columnName string, originalColumn *Column) DiffDeque { diffsQueue := NewDiffDeque() column := table.Columns[columnName] + + if !reflect.DeepEqual(column.Parameters, originalColumn.Parameters) { + diffsQueue.Add(NewChangeColumnParameterDiff(table.Name, columnName, originalColumn, column)) + } + if column.IsNotNull != originalColumn.IsNotNull { if column.IsNotNull { diffsQueue.Add(NewMakeColumnNotNullDiff(table.Name, columnName, column)) @@ -57,5 +64,6 @@ func (c *Column) Diff(table *Table, columnName string, originalColumn *Column) D diffsQueue.Add(NewMakeColumnNullableDiff(table.Name, columnName, column)) } } + return diffsQueue } diff --git a/domain/driver.go b/domain/driver.go index 4231a5e..b835558 100644 --- a/domain/driver.go +++ b/domain/driver.go @@ -38,5 +38,7 @@ type Deparser interface { MakeColumnNotNull(tableName, columnName string, column *Column) string MakeColumnNullable(tableName, columnName string, column *Column) string + ChangeDataTypeParameters(tableName, columnName string, column *Column) string + WriteScript(statements []string) string } diff --git a/drivers/mysql/deparser.go b/drivers/mysql/deparser.go index 72d80c3..194c694 100644 --- a/drivers/mysql/deparser.go +++ b/drivers/mysql/deparser.go @@ -61,6 +61,16 @@ func (d *deparser) MakeColumnNullable(tableName, columnName string, column *doma return fmt.Sprintf("ALTER TABLE %s MODIFY %s NULL", tableName, columnDatatype) } +func (d *deparser) ChangeDataTypeParameters(tableName, columnName string, column *domain.Column) string { + _, hasSize := column.Parameters["size"] + + if hasSize { + return fmt.Sprintf("ALTER TABLE %s MODIFY %s %s(%s)", tableName, columnName, column.Datatype, fmt.Sprint(column.Parameters["size"])) + } else { + return fmt.Sprintf("ALTER TABLE %s MODIFY %s %s(%s, %s)", tableName, columnName, column.Datatype, fmt.Sprint(column.Parameters["precision"]), fmt.Sprint(column.Parameters["scale"])) + } +} + func (d *deparser) WriteScript(statements []string) string { var scriptContent string for _, s := range statements { diff --git a/drivers/postgres/deparser.go b/drivers/postgres/deparser.go index 33a6646..f1e7c30 100644 --- a/drivers/postgres/deparser.go +++ b/drivers/postgres/deparser.go @@ -58,14 +58,14 @@ func (d *deparser) CreateTable(table *domain.Table) string { } if column.IsNotNull { - statement = statement + fmt.Sprintf(" NOT NULL") + statement = statement + " NOT NULL" } - statement = statement + fmt.Sprintf(",\n") + statement = statement + ",\n" } statement = statement[0 : len(statement)-2] - statement = statement + fmt.Sprintf("\n)") + statement = statement + "\n)" return statement } @@ -87,6 +87,7 @@ func (d *deparser) AddColumn(tableName, columnName string, column *domain.Column func (d *deparser) DropColumn(tableName, columnName string) string { return fmt.Sprintf("ALTER TABLE %s DROP COLUMN %s", tableName, columnName) } + func (d *deparser) MakeColumnNotNull(tableName, columnName string, column *domain.Column) string { return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL", tableName, columnName) } @@ -95,6 +96,16 @@ func (d *deparser) MakeColumnNullable(tableName, columnName string, column *doma return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL", tableName, columnName) } +func (d *deparser) ChangeDataTypeParameters(tableName, columnName string, column *domain.Column) string { + _, hasSize := column.Parameters["size"] + + if hasSize { + return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s TYPE %s(%s)", tableName, columnName, column.Datatype, fmt.Sprint(column.Parameters["size"])) + } else { + return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s TYPE %s(%s, %s)", tableName, columnName, column.Datatype, fmt.Sprint(column.Parameters["precision"]), fmt.Sprint(column.Parameters["scale"])) + } +} + func (d *deparser) WriteScript(statements []string) string { var scriptContent string for _, s := range statements { diff --git a/drivers/postgres/parser.go b/drivers/postgres/parser.go index 5f15f3b..1cad026 100644 --- a/drivers/postgres/parser.go +++ b/drivers/postgres/parser.go @@ -265,10 +265,15 @@ func (d *postgresDriver) parseColumn(columnDefinition *pg_query.ColumnDef) *doma datatype := columnDefinition.TypeName.Names[1].GetString_().GetStr() parameters := make(map[string]interface{}) - if datatype == "varchar" { + if hasSingleArg(datatype) { parameters["size"] = columnDefinition.TypeName.Typmods[0].GetAConst().Val.GetInteger().Ival } + if hasDoubleArg(datatype) { + parameters["precision"] = columnDefinition.TypeName.Typmods[0].GetAConst().Val.GetInteger().Ival + parameters["scale"] = columnDefinition.TypeName.Typmods[1].GetAConst().Val.GetInteger().Ival + } + isNotNull := false for _, constraint := range columnDefinition.Constraints { if constraint.GetConstraint().GetContype().String() == "CONSTR_NOTNULL" { diff --git a/drivers/postgres/postgres_test.go b/drivers/postgres/postgres_test.go index cac4090..27efb96 100644 --- a/drivers/postgres/postgres_test.go +++ b/drivers/postgres/postgres_test.go @@ -26,9 +26,10 @@ var _ = Describe("Postgres Driver", func() { CREATE TABLE users ( social_number VARCHAR(9) PRIMARY KEY, phone VARCHAR(11), - name VARCHAR(15), + name VARCHAR(15) NOT NULL, age INTEGER, size INT, + height DECIMAL(2, 3), ddi VARCHAR(3) ); @@ -44,10 +45,11 @@ var _ = Describe("Postgres Driver", func() { Columns: map[string]*domain.Column{ "social_number": {Datatype: "varchar", IsNotNull: false, Parameters: map[string]interface{}{"size": int32(9)}}, "phone": {Datatype: "varchar", IsNotNull: false, Parameters: map[string]interface{}{"size": int32(11)}}, - "name": {Datatype: "varchar", IsNotNull: false, Parameters: map[string]interface{}{"size": int32(15)}}, + "name": {Datatype: "varchar", IsNotNull: true, Parameters: map[string]interface{}{"size": int32(15)}}, "ddi": {Datatype: "varchar", IsNotNull: false, Parameters: map[string]interface{}{"size": int32(3)}}, "age": {Datatype: "int4", IsNotNull: false, Parameters: map[string]interface{}{}}, "size": {Datatype: "int4", IsNotNull: false, Parameters: map[string]interface{}{}}, + "height": {Datatype: "numeric", IsNotNull: false, Parameters: map[string]interface{}{"precision": int32(2), "scale": int32(3)}}, }, }, }, @@ -128,6 +130,24 @@ var _ = Describe("Postgres Driver", func() { }) }) + When("A column's datatype parameters change", func() { + It("Changes single parameter datatype", func() { + columnName := "col" + tableName := "tbl" + column := &domain.Column{Datatype: "varchar", IsNotNull: false, Parameters: map[string]interface{}{"size": int32(10)}} + stmt := subject.ChangeDataTypeParameters(tableName, columnName, column) + Expect(strings.ToLower(stmt)).To(Equal("alter table tbl alter column col type varchar(10)")) + }) + + It("Changes double parameter datatype", func() { + columnName := "col" + tableName := "tbl" + column := &domain.Column{Datatype: "decimal", IsNotNull: false, Parameters: map[string]interface{}{"precision": int32(2), "scale": int32(3)}} + stmt := subject.ChangeDataTypeParameters(tableName, columnName, column) + Expect(strings.ToLower(stmt)).To(Equal("alter table tbl alter column col type decimal(2, 3)")) + }) + }) + When("New schema doesn't have a table", func() { It("Drops the table", func() { tableName := "tbl" diff --git a/mock/domain/driver_mock.go b/mock/domain/driver_mock.go index 9d67e6e..5c81e93 100644 --- a/mock/domain/driver_mock.go +++ b/mock/domain/driver_mock.go @@ -329,6 +329,20 @@ func (mr *MockDeparserMockRecorder) AddColumn(tableName, columnName, column inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddColumn", reflect.TypeOf((*MockDeparser)(nil).AddColumn), tableName, columnName, column) } +// ChangeDataTypeParameters mocks base method. +func (m *MockDeparser) ChangeDataTypeParameters(tableName, columnName string, column *domain.Column) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChangeDataTypeParameters", tableName, columnName, column) + ret0, _ := ret[0].(string) + return ret0 +} + +// ChangeDataTypeParameters indicates an expected call of ChangeDataTypeParameters. +func (mr *MockDeparserMockRecorder) ChangeDataTypeParameters(tableName, columnName, column interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeDataTypeParameters", reflect.TypeOf((*MockDeparser)(nil).ChangeDataTypeParameters), tableName, columnName, column) +} + // CreateTable mocks base method. func (m *MockDeparser) CreateTable(table *domain.Table) string { m.ctrl.T.Helper() diff --git a/testdata/0000_schema.sql b/testdata/0000_schema.sql new file mode 100644 index 0000000..e69de29 diff --git a/testdata/0001_schema.sql b/testdata/0001_schema.sql new file mode 100644 index 0000000..065bb77 --- /dev/null +++ b/testdata/0001_schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE users ( + social_number VARCHAR(9) PRIMARY KEY, + nombre VARCHAR(20) NOT NULL, + phone VARCHAR(20), + ddi VARCHAR(20), + num DECIMAL(20, 10) +); + +CREATE TABLE products ( + nombre VARCHAR(256), + qnt INTEGER +); diff --git a/testdata/0002_schema.sql b/testdata/0002_schema.sql new file mode 100644 index 0000000..6c1b6ab --- /dev/null +++ b/testdata/0002_schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE users ( + social_number VARCHAR(9) PRIMARY KEY, + nombre VARCHAR(15), + phone VARCHAR(11), + num DECIMAL(10, 5) +); diff --git a/testdata/new_schema.sql b/testdata/new_schema.sql deleted file mode 100644 index a18c75b..0000000 --- a/testdata/new_schema.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE users ( - social_number VARCHAR(9) PRIMARY KEY, - name VARCHAR(15), - phone VARCHAR(11), - ddi VARCHAR(3) -); - -CREATE TABLE products ( - name VARCHAR(256), - qnt INTEGER -); diff --git a/testdata/old_schema.sql b/testdata/old_schema.sql deleted file mode 100644 index 38363bd..0000000 --- a/testdata/old_schema.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE users ( - social_number VARCHAR(9) PRIMARY KEY, - name VARCHAR(15), - phone VARCHAR(11) -); diff --git a/testdata/output/0001_1656197671.down.sql b/testdata/output/0001_1656197671.down.sql new file mode 100644 index 0000000..c99ddcd --- /dev/null +++ b/testdata/output/0001_1656197671.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS users; diff --git a/testdata/output/0001_1656197671.up.sql b/testdata/output/0001_1656197671.up.sql new file mode 100644 index 0000000..725c493 --- /dev/null +++ b/testdata/output/0001_1656197671.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE users ( +nombre varchar(15), +num numeric(10,5), +phone varchar(11), +social_number varchar(9) +); diff --git a/testdata/output/0002_1656197676.down.sql b/testdata/output/0002_1656197676.down.sql new file mode 100644 index 0000000..a2c3974 --- /dev/null +++ b/testdata/output/0002_1656197676.down.sql @@ -0,0 +1,6 @@ +ALTER TABLE users DROP COLUMN ddi; +ALTER TABLE users ALTER COLUMN num TYPE numeric(10, 5); +ALTER TABLE users ALTER COLUMN nombre TYPE varchar(15); +ALTER TABLE users ALTER COLUMN nombre DROP NOT NULL; +ALTER TABLE users ALTER COLUMN phone TYPE varchar(11); +DROP TABLE IF EXISTS products; diff --git a/testdata/output/0002_1656197676.up.sql b/testdata/output/0002_1656197676.up.sql new file mode 100644 index 0000000..36359ca --- /dev/null +++ b/testdata/output/0002_1656197676.up.sql @@ -0,0 +1,9 @@ +ALTER TABLE users ADD COLUMN ddi varchar(20); +ALTER TABLE users ALTER COLUMN num TYPE numeric(20, 10); +ALTER TABLE users ALTER COLUMN nombre TYPE varchar(20); +ALTER TABLE users ALTER COLUMN nombre SET NOT NULL; +ALTER TABLE users ALTER COLUMN phone TYPE varchar(20); +CREATE TABLE products ( +nombre varchar(256), +qnt int4 +); diff --git a/testdata/output/down_migration.sql b/testdata/output/down_migration.sql deleted file mode 100644 index fd69ec0..0000000 --- a/testdata/output/down_migration.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP TABLE IF EXISTS products; -ALTER TABLE users DROP COLUMN ddi; diff --git a/testdata/output/up_migration.sql b/testdata/output/up_migration.sql deleted file mode 100644 index 6467050..0000000 --- a/testdata/output/up_migration.sql +++ /dev/null @@ -1,2 +0,0 @@ -; -;