diff --git a/krab/cmd_gen_migration.go b/krab/cmd_gen_migration.go index fbe9d36..6e1a2bc 100644 --- a/krab/cmd_gen_migration.go +++ b/krab/cmd_gen_migration.go @@ -55,31 +55,10 @@ func (c *CmdGenMigration) Do(ctx context.Context, o CmdOpts) (interface{}, error if err != nil { return nil, err } - kcls := []ToKCL{} - kclsAfter := []ToKCL{} - for _, v := range o.PositionalInputs { - splits := strings.Split(v, ":") - if len(splits) == 1 { - switch splits[0] { - case "id": - kcls = append(kcls, &DDLColumn{Name: "id", Type: "bigint", Null: true, Identity: &DDLIdentity{}}) - kclsAfter = append(kclsAfter, &DDLPrimaryKey{Columns: []string{"id"}}) - case "timestamps": - kcls = append(kcls, &DDLColumn{Name: "created_at", Type: "timestamptz", Null: false}) - kcls = append(kcls, &DDLColumn{Name: "updated_at", Type: "timestamptz", Null: false}) - default: - return nil, fmt.Errorf("Invalid column: %s", splits[0]) - } - } else { - kcls = append(kcls, &DDLColumn{Name: splits[0], Type: splits[1], Null: true}) - } - } - - kcls = append(kcls, kclsAfter...) - return c.run(ctx, o.NamedInputs, kcls) + return c.run(ctx, o) } -func (c *CmdGenMigration) run(ctx context.Context, inputs NamedInputs, columns []ToKCL) (ResponseGenMigration, error) { +func (c *CmdGenMigration) run(ctx context.Context, o CmdOpts) (ResponseGenMigration, error) { result := ResponseGenMigration{} dir, err := krabenv.ConfigDir() @@ -92,40 +71,60 @@ func (c *CmdGenMigration) run(ctx context.Context, inputs NamedInputs, columns [ return result, err } + columns := []*DDLColumn{} + pks := []*DDLPrimaryKey{} + for _, v := range o.PositionalInputs { + splits := strings.Split(v, ":") + if len(splits) == 1 { + switch splits[0] { + case "id": + columns = append(columns, &DDLColumn{Name: "id", Type: "bigint", Null: true, Identity: &DDLIdentity{}}) + pks = append(pks, &DDLPrimaryKey{Columns: []string{"id"}}) + case "timestamps": + columns = append(columns, &DDLColumn{Name: "created_at", Type: "timestamptz", Null: false}) + columns = append(columns, &DDLColumn{Name: "updated_at", Type: "timestamptz", Null: false}) + default: + return result, fmt.Errorf("Invalid column: %s", splits[0]) + } + } else { + columns = append(columns, &DDLColumn{Name: splits[0], Type: splits[1], Null: true}) + } + } + + ref := o.NamedInputs["name"].(string) + table := ref + words := strings.SplitN(ref, "_", 2) + if len(words) == 2 && words[0] == "create" { + table = words[1] + } + version := c.VersionGenerator.Next() - ref := inputs["name"].(string) + migration := &Migration{ + RefName: ref, + Version: version, + Up: MigrationUpOrDown{ + CreateTables: []*DDLCreateTable{ + { + Name: table, + Columns: columns, + PrimaryKeys: pks, + }, + }, + }, + Down: MigrationUpOrDown{ + DropTables: []*DDLDropTable{ + { + Name: table, + }, + }, + }, + } + result.Ref = fmt.Sprint("migration.", ref) result.Path = filepath.Join(dir, fmt.Sprint(version, "_", ref, krabenv.Ext())) buf := bytes.Buffer{} - buf.WriteString(`migration "`) - buf.WriteString(ref) - buf.WriteString(`" {`) - buf.WriteString("\n") - buf.WriteString(` version = "`) - buf.WriteString(version) - buf.WriteString(`"`) - buf.WriteString("\n\n") - buf.WriteString(` up {`) - buf.WriteString("\n") - for _, col := range columns { - sb := strings.Builder{} - col.ToKCL(&sb) - lines := strings.Split(sb.String(), "\n") - for _, line := range lines { - buf.WriteString(" ") - buf.WriteString(line) - buf.WriteString("\n") - } - } - buf.WriteString(` }`) - buf.WriteString("\n\n") - buf.WriteString(` down {`) - buf.WriteString("\n") - buf.WriteString(` }`) - buf.WriteString("\n") - buf.WriteString(`}`) - buf.WriteString("\n") + migration.ToKCL(&buf) c.FS.WriteFile(result.Path, buf.Bytes(), 0644) diff --git a/krab/type_ddl_column.go b/krab/type_ddl_column.go index f51a7e9..94eaf6f 100644 --- a/krab/type_ddl_column.go +++ b/krab/type_ddl_column.go @@ -128,16 +128,28 @@ func (d *DDLColumn) ToSQL(w io.StringWriter) { // ToKCL converts migration definition to KCL. func (d *DDLColumn) ToKCL(w io.StringWriter) { - w.WriteString("column ") + w.WriteString(" column ") w.WriteString(krabdb.QuoteIdent(d.Name)) w.WriteString(" ") w.WriteString(krabdb.QuoteIdent(d.Type)) w.WriteString(" {") + multiline := false if d.Identity != nil { - w.WriteString("\n identity {}\n") + w.WriteString("\n") + d.Identity.ToKCL(w) + w.WriteString("\n") + multiline = true } if !d.Null { - w.WriteString("\n null = false\n") + w.WriteString("\n") + w.WriteString(" null = false") + w.WriteString("\n") + multiline = true } - w.WriteString("}") + if multiline { + w.WriteString(" }") + } else { + w.WriteString("}") + } + w.WriteString("\n") } diff --git a/krab/type_ddl_create_table.go b/krab/type_ddl_create_table.go index abb4be2..dbe04c9 100644 --- a/krab/type_ddl_create_table.go +++ b/krab/type_ddl_create_table.go @@ -182,3 +182,20 @@ func (d *DDLCreateTable) ToSQL(w io.StringWriter) { w.WriteString("\n)") } + +// ToKCL converts migration definition to KCL. +func (d *DDLCreateTable) ToKCL(w io.StringWriter) { + w.WriteString(" create_table ") + w.WriteString(krabdb.QuoteIdent(d.Name)) + w.WriteString(" {") + w.WriteString("\n") + + for _, def := range d.Columns { + def.ToKCL(w) + } + for _, def := range d.PrimaryKeys { + def.ToKCL(w) + } + w.WriteString(" }") + w.WriteString("\n") +} diff --git a/krab/type_ddl_drop_table.go b/krab/type_ddl_drop_table.go index 8594ae5..75607bb 100644 --- a/krab/type_ddl_drop_table.go +++ b/krab/type_ddl_drop_table.go @@ -53,3 +53,9 @@ func (d *DDLDropTable) ToSQL(w io.StringWriter) { w.WriteString("DROP TABLE ") w.WriteString(krabdb.QuoteIdent(d.Name)) } + +func (d *DDLDropTable) ToKCL(w io.StringWriter) { + w.WriteString(" drop_table ") + w.WriteString(krabdb.QuoteIdent(d.Name)) + w.WriteString(" {}\n") +} diff --git a/krab/type_ddl_identity.go b/krab/type_ddl_identity.go index 1d7974a..977786d 100644 --- a/krab/type_ddl_identity.go +++ b/krab/type_ddl_identity.go @@ -50,3 +50,8 @@ func (d *DDLIdentity) DecodeHCL(ctx *hcl.EvalContext, block *hcl.Block) error { func (d *DDLIdentity) ToSQL(w io.StringWriter) { w.WriteString("GENERATED ALWAYS AS IDENTITY") } + +// ToKCL converts migration definition to KCL. +func (d *DDLIdentity) ToKCL(w io.StringWriter) { + w.WriteString(" identity {}") +} diff --git a/krab/type_ddl_primary_key.go b/krab/type_ddl_primary_key.go index c6f9db4..377b1fb 100644 --- a/krab/type_ddl_primary_key.go +++ b/krab/type_ddl_primary_key.go @@ -89,9 +89,9 @@ func (d *DDLPrimaryKey) ToSQL(w io.StringWriter) { // ToKCL converts migration definition to KCL. func (d *DDLPrimaryKey) ToKCL(w io.StringWriter) { - w.WriteString("primary_key {\n") - w.WriteString(" columns = [") - w.WriteString(strings.Join(krabdb.QuoteIdentStrings(d.Columns), ", ")) - w.WriteString("]") - w.WriteString("\n}") + w.WriteString(" primary_key {\n") + w.WriteString(" columns = [") + w.WriteString(strings.Join(krabdb.QuoteIdentStrings(d.Columns), "")) + w.WriteString("]\n") + w.WriteString(" }\n") } diff --git a/krab/type_migration.go b/krab/type_migration.go index 55ceb16..ce52a54 100644 --- a/krab/type_migration.go +++ b/krab/type_migration.go @@ -5,11 +5,11 @@ import ( "io" "github.com/hashicorp/hcl/v2" + "github.com/ohkrab/krab/krabdb" "github.com/ohkrab/krab/krabhcl" ) // Migration represents single up/down migration pair. -// type Migration struct { krabhcl.Source @@ -138,14 +138,33 @@ func (m *Migration) DecodeHCL(ctx *hcl.EvalContext, block *hcl.Block) error { return nil } -func (ms *Migration) Validate() error { +func (m *Migration) Validate() error { return ErrorCoalesce( - ValidateRefName(ms.RefName), - ms.Up.Validate(), - ms.Down.Validate(), + ValidateRefName(m.RefName), + m.Up.Validate(), + m.Down.Validate(), ) } +func (m *Migration) ToKCL(w io.StringWriter) { + w.WriteString("migration ") + w.WriteString(krabdb.QuoteIdent(m.RefName)) + w.WriteString(" {\n") + w.WriteString(" version = ") + w.WriteString(krabdb.QuoteIdent(m.Version)) + w.WriteString("\n\n") + + w.WriteString(" up {\n") + m.Up.ToKCL(w) + w.WriteString(" }\n\n") + + w.WriteString(" down {\n") + m.Down.ToKCL(w) + w.WriteString(" }\n") + + w.WriteString("}") +} + // DecodeHCL parses HCL into struct. func (m *MigrationUpOrDown) DecodeHCL(ctx *hcl.EvalContext, block *hcl.Block) error { m.Source.Extract(block) @@ -227,6 +246,15 @@ func (m *MigrationUpOrDown) ToSQL(w io.StringWriter) { w.WriteString(m.SQL) } +func (m *MigrationUpOrDown) ToKCL(w io.StringWriter) { + for _, t := range m.CreateTables { + t.ToKCL(w) + } + for _, t := range m.DropTables { + t.ToKCL(w) + } +} + // ToSQLStatements returns list of SQL statements to executre during the migration. func (m *MigrationUpOrDown) ToSQLStatements() SQLStatements { sorter := SQLStatementsSorter{Statements: SQLStatements{}, Bytes: []int{}} diff --git a/spec/action_gen_migration_test.go b/spec/action_gen_migration_test.go index 8aec159..69cb45c 100644 --- a/spec/action_gen_migration_test.go +++ b/spec/action_gen_migration_test.go @@ -19,9 +19,12 @@ func TestActionGenMigration(t *testing.T) { version = "20230101" up { + create_table "maps" { + } } down { + drop_table "maps" {} } }` ok, err := c.fs.FileContainsBytes(k, []byte(expected)) @@ -39,7 +42,7 @@ func TestActionGenMigrationWithParams(t *testing.T) { defer c.Teardown() c.AssertSuccessfulRun(t, []string{ "gen", "migration", "-name", "create_maps", - "id", "name:varchar", "project_id:bigint:fk=projects,id", "timestamps", + "id", "name:varchar", "project_id:bigint", "timestamps", }) c.AssertOutputContains(t, "migration.create_maps") files := c.FSFiles() @@ -54,24 +57,15 @@ func TestActionGenMigrationWithParams(t *testing.T) { identity {} } column "name" "varchar" {} + column "project_id" "bigint" {} column "created_at" "timestamptz" { - null = false - } + null = false + } column "updated_at" "timestamptz" { - null = false - } + null = false + } primary_key { - columns = ["id"] - } - foreign_key { - columns = ["project_id"] - - references "projects" { - columns = ["id"] - - on_delete = "restrict" - on_update = "cascade" - } + columns = ["id"] } } }