From f7989cb6a7f8c7dcf227e6905ea3ea27462c70ae Mon Sep 17 00:00:00 2001 From: Aaron L Date: Fri, 8 Jun 2018 22:47:18 -0700 Subject: [PATCH] Begin alias work - Add the foreign key name to the relationships structs from the driver - Modify text helpers to do a lot less work. - Add an Aliases type in the configuration to allow specifying aliases as well as carrying the aliases into the templates. This will now be used everywhere for all Go type names inside the templates. - Add tests for the new config pieces. --- boilingcore/aliases.go | 160 +++++++++++++++++++++ boilingcore/aliases_test.go | 235 +++++++++++++++++++++++++++++++ boilingcore/boilingcore.go | 97 +++++++------ boilingcore/config.go | 119 ++++++++++++---- boilingcore/config_test.go | 146 +++++++++++++++++++ boilingcore/templates.go | 5 +- boilingcore/text_helpers.go | 28 ++-- boilingcore/text_helpers_test.go | 8 +- drivers/relationships.go | 10 ++ drivers/relationships_test.go | 10 ++ main.go | 1 + 11 files changed, 731 insertions(+), 88 deletions(-) create mode 100644 boilingcore/aliases.go create mode 100644 boilingcore/aliases_test.go create mode 100644 boilingcore/config_test.go diff --git a/boilingcore/aliases.go b/boilingcore/aliases.go new file mode 100644 index 000000000..e1462f025 --- /dev/null +++ b/boilingcore/aliases.go @@ -0,0 +1,160 @@ +package boilingcore + +import ( + "fmt" + + "github.com/volatiletech/sqlboiler/drivers" + "github.com/volatiletech/sqlboiler/strmangle" +) + +// Aliases defines aliases for the generation run +type Aliases struct { + Tables map[string]TableAlias `toml:"tables,omitempty" json:"tables,omitempty"` + Relationships map[string]RelationshipAlias `toml:"relationships,omitempty" json:"relationships,omitempty"` +} + +// TableAlias defines the spellings for a table name in Go +type TableAlias struct { + UpPlural string `toml:"up_plural,omitempty" json:"up_plural,omitempty"` + UpSingular string `toml:"up_singular,omitempty" json:"up_singular,omitempty"` + DownPlural string `toml:"down_plural,omitempty" json:"down_plural,omitempty"` + DownSingular string `toml:"down_singular,omitempty" json:"down_singular,omitempty"` + + Columns map[string]string `toml:"columns,omitempty" json:"columns,omitempty"` +} + +// RelationshipAlias defines the naming for both sides of +// a foreign key. +type RelationshipAlias struct { + Local string `toml:"local,omitempty" json:"local,omitempty"` + Foreign string `toml:"foreign,omitempty" json:"foreign,omitempty"` +} + +// FillAliases takes the table information from the driver +// and fills in aliases where the user has provided none. +// +// This leaves us with a complete list of Go names for all tables, +// columns, and relationships. +func FillAliases(a *Aliases, tables []drivers.Table) { + for _, t := range tables { + if t.IsJoinTable { + continue + } + + if a.Tables == nil { + a.Tables = make(map[string]TableAlias) + } + + table := a.Tables[t.Name] + + if len(table.UpPlural) == 0 { + table.UpPlural = strmangle.TitleCase(strmangle.Plural(t.Name)) + } + if len(table.UpSingular) == 0 { + table.UpSingular = strmangle.TitleCase(strmangle.Singular(t.Name)) + } + if len(table.DownPlural) == 0 { + table.DownPlural = strmangle.CamelCase(strmangle.Plural(t.Name)) + } + if len(table.DownSingular) == 0 { + table.DownSingular = strmangle.CamelCase(strmangle.Singular(t.Name)) + } + + if table.Columns == nil { + table.Columns = make(map[string]string) + } + + for _, c := range t.Columns { + if _, ok := table.Columns[c.Name]; !ok { + table.Columns[c.Name] = strmangle.TitleCase(c.Name) + } + } + + a.Tables[t.Name] = table + + if a.Relationships == nil { + a.Relationships = make(map[string]RelationshipAlias) + } + + for _, k := range t.FKeys { + r := a.Relationships[k.Name] + if len(r.Local) != 0 && len(r.Foreign) != 0 { + continue + } + + local, foreign := txtNameToOne(k) + if len(r.Local) == 0 { + r.Local = local + } + if len(r.Foreign) == 0 { + r.Foreign = foreign + } + + a.Relationships[k.Name] = r + } + + for _, rel := range t.ToManyRelationships { + localFacingAlias, okLocal := a.Relationships[rel.JoinLocalFKeyName] + foreignFacingAlias, okForeign := a.Relationships[rel.JoinForeignFKeyName] + + if okLocal && okForeign { + continue + } + + local, foreign := txtNameToMany(rel) + + switch { + case !okLocal && !okForeign: + localFacingAlias.Local = local + localFacingAlias.Foreign = foreign + foreignFacingAlias.Local = foreign + foreignFacingAlias.Foreign = local + case okLocal: + if len(localFacingAlias.Local) == 0 { + localFacingAlias.Local = local + } + if len(localFacingAlias.Foreign) == 0 { + localFacingAlias.Foreign = foreign + } + + foreignFacingAlias.Local = localFacingAlias.Foreign + foreignFacingAlias.Foreign = localFacingAlias.Local + case okForeign: + if len(foreignFacingAlias.Local) == 0 { + foreignFacingAlias.Local = foreign + } + if len(foreignFacingAlias.Foreign) == 0 { + foreignFacingAlias.Foreign = local + } + + localFacingAlias.Foreign = foreignFacingAlias.Local + localFacingAlias.Local = foreignFacingAlias.Foreign + } + + a.Relationships[rel.JoinLocalFKeyName] = localFacingAlias + a.Relationships[rel.JoinForeignFKeyName] = foreignFacingAlias + } + } +} + +// Table gets a table alias, panics if not found. +func (a Aliases) Table(table string) TableAlias { + t, ok := a.Tables[table] + if !ok { + panic("could not find table aliases for: " + table) + } + + return t +} + +// Column gets a column's aliased name, panics if not found. +func (a Aliases) Column(table, column string) string { + t := a.Table(table) + + c, ok := t.Columns[column] + if !ok { + panic(fmt.Sprintf("could not find column alias for: %s.%s", table, column)) + } + + return c +} diff --git a/boilingcore/aliases_test.go b/boilingcore/aliases_test.go new file mode 100644 index 000000000..081c8dad1 --- /dev/null +++ b/boilingcore/aliases_test.go @@ -0,0 +1,235 @@ +package boilingcore + +import ( + "reflect" + "testing" + + "github.com/volatiletech/sqlboiler/drivers" +) + +func TestAliasesTables(t *testing.T) { + t.Parallel() + + tables := []drivers.Table{ + { + Name: "videos", + Columns: []drivers.Column{ + {Name: "id"}, + {Name: "name"}, + }, + }, + } + + t.Run("Automatic", func(t *testing.T) { + expect := TableAlias{ + UpPlural: "Videos", + UpSingular: "Video", + DownPlural: "videos", + DownSingular: "video", + Columns: map[string]string{ + "id": "ID", + "name": "Name", + }, + } + + a := Aliases{} + FillAliases(&a, tables) + + if got := a.Tables["videos"]; !reflect.DeepEqual(expect, got) { + t.Errorf("it should fill in the blanks: %#v", got) + } + }) + + t.Run("UserOverride", func(t *testing.T) { + expect := TableAlias{ + UpPlural: "NotVideos", + UpSingular: "NotVideo", + DownPlural: "notVideos", + DownSingular: "notVideo", + Columns: map[string]string{ + "id": "NotID", + "name": "NotName", + }, + } + + a := Aliases{} + a.Tables = map[string]TableAlias{"videos": expect} + FillAliases(&a, tables) + + if !reflect.DeepEqual(expect, a.Tables["videos"]) { + t.Error("it should not alter things that were specified by user") + } + }) +} + +func TestAliasesRelationships(t *testing.T) { + t.Parallel() + + tables := []drivers.Table{ + { + Name: "videos", + Columns: []drivers.Column{ + {Name: "id"}, + {Name: "name"}, + }, + FKeys: []drivers.ForeignKey{ + { + Name: "fkey_1", + Table: "videos", + Column: "user_id", + ForeignTable: "users", + ForeignColumn: "id", + }, + { + Name: "fkey_2", + Table: "videos", + Column: "publisher_id", + ForeignTable: "users", + ForeignColumn: "id", + }, + { + Name: "fkey_3", + Table: "videos", + Column: "one_id", + Unique: true, + ForeignTable: "ones", + ForeignColumn: "one", + }, + }, + }, + } + + t.Run("Automatic", func(t *testing.T) { + expect1 := RelationshipAlias{ + Local: "User", + Foreign: "Videos", + } + expect2 := RelationshipAlias{ + Local: "Publisher", + Foreign: "PublisherVideos", + } + expect3 := RelationshipAlias{ + Local: "One", + Foreign: "Video", + } + + a := Aliases{} + FillAliases(&a, tables) + + if got := a.Relationships["fkey_1"]; !reflect.DeepEqual(expect1, got) { + t.Errorf("bad values: %#v", got) + } + if got := a.Relationships["fkey_2"]; !reflect.DeepEqual(expect2, got) { + t.Errorf("bad values: %#v", got) + } + if got := a.Relationships["fkey_3"]; !reflect.DeepEqual(expect3, got) { + t.Errorf("bad values: %#v", got) + } + }) + + t.Run("UserOverride", func(t *testing.T) { + expect1 := RelationshipAlias{ + Local: "TheUser", + Foreign: "Videos", + } + expect2 := RelationshipAlias{ + Local: "Publisher", + Foreign: "PublishedVideos", + } + expect3 := RelationshipAlias{ + Local: "TheOne", + Foreign: "AwesomeOneVideo", + } + + a := Aliases{ + Relationships: map[string]RelationshipAlias{ + "fkey_1": {Local: "TheUser"}, + "fkey_2": {Foreign: "PublishedVideos"}, + "fkey_3": {Local: "TheOne", Foreign: "AwesomeOneVideo"}, + }, + } + FillAliases(&a, tables) + + if got := a.Relationships["fkey_1"]; !reflect.DeepEqual(expect1, got) { + t.Errorf("bad values: %#v", got) + } + if got := a.Relationships["fkey_2"]; !reflect.DeepEqual(expect2, got) { + t.Errorf("bad values: %#v", got) + } + if got := a.Relationships["fkey_3"]; !reflect.DeepEqual(expect3, got) { + t.Errorf("bad values: %#v", got) + } + }) +} + +func TestAliasesRelationshipsJoinTable(t *testing.T) { + t.Parallel() + + tables := []drivers.Table{ + { + Name: "videos", + ToManyRelationships: []drivers.ToManyRelationship{ + { + Table: "videos", + ForeignTable: "tags", + Column: "id", + ForeignColumn: "id", + + ToJoinTable: true, + JoinTable: "video_tags", + + JoinLocalFKeyName: "fk_video_id", + JoinLocalColumn: "video_id", + JoinForeignFKeyName: "fk_tag_id", + JoinForeignColumn: "tag_id", + }, + }, + }, + } + + t.Run("Automatic", func(t *testing.T) { + expect1 := RelationshipAlias{ + Local: "Tags", + Foreign: "Videos", + } + expect2 := RelationshipAlias{ + Local: "Videos", + Foreign: "Tags", + } + + a := Aliases{} + FillAliases(&a, tables) + + if got := a.Relationships["fk_video_id"]; !reflect.DeepEqual(expect1, got) { + t.Errorf("bad values: %#v", got) + } + if got := a.Relationships["fk_tag_id"]; !reflect.DeepEqual(expect2, got) { + t.Errorf("bad values: %#v", got) + } + }) + + t.Run("UserOverride", func(t *testing.T) { + expect1 := RelationshipAlias{ + Local: "NotTags", + Foreign: "NotVideos", + } + expect2 := RelationshipAlias{ + Local: "NotVideos", + Foreign: "NotTags", + } + + a := Aliases{ + Relationships: map[string]RelationshipAlias{ + "fk_video_id": {Local: "NotTags", Foreign: "NotVideos"}, + }, + } + FillAliases(&a, tables) + + if got := a.Relationships["fk_video_id"]; !reflect.DeepEqual(expect1, got) { + t.Errorf("bad values: %#v", got) + } + if got := a.Relationships["fk_tag_id"]; !reflect.DeepEqual(expect2, got) { + t.Errorf("bad values: %#v", got) + } + }) +} diff --git a/boilingcore/boilingcore.go b/boilingcore/boilingcore.go index 4b884ac9c..b9f9d2cff 100644 --- a/boilingcore/boilingcore.go +++ b/boilingcore/boilingcore.go @@ -28,6 +28,11 @@ const ( templatesTestMainDirectory = "templates_test/main_test" ) +var ( + // Tags must be in a format like: json, xml, etc. + rgxValidTag = regexp.MustCompile(`[a-zA-Z_\.]+`) +) + // State holds the global data needed by most pieces to run type State struct { Config *Config @@ -69,20 +74,12 @@ func New(config *Config) (*State, error) { return nil, err } - if s.Config.Debug { - b, err := json.Marshal(s.Tables) - if err != nil { - return nil, errors.Wrap(err, "unable to json marshal tables") - } - fmt.Printf("%s\n", b) - } - err = s.initOutFolder() if err != nil { return nil, errors.Wrap(err, "unable to initialize the output folder") } - err = s.initTemplates() + templates, err := s.initTemplates() if err != nil { return nil, errors.Wrap(err, "unable to initialize templates") } @@ -92,6 +89,29 @@ func New(config *Config) (*State, error) { return nil, errors.Wrap(err, "unable to initialize struct tags") } + err = s.initAliases(config.Aliases) + if err != nil { + return nil, errors.Wrap(err, "unable to initialize aliases") + } + + if s.Config.Debug { + debugOut := struct { + Config *Config `json:"config"` + Tables []drivers.Table `json:"tables"` + Templates []lazyTemplate `json:"templates"` + }{ + Config: s.Config, + Tables: s.Tables, + Templates: templates, + } + + b, err := json.Marshal(debugOut) + if err != nil { + return nil, errors.Wrap(err, "unable to json marshal tables") + } + fmt.Printf("%s\n", b) + } + return s, nil } @@ -100,6 +120,7 @@ func New(config *Config) (*State, error) { func (s *State) Run() error { data := &templateData{ Tables: s.Tables, + Aliases: s.Config.Aliases, DriverName: s.Config.DriverName, PkgName: s.Config.PkgName, AddGlobal: s.Config.AddGlobal, @@ -157,21 +178,23 @@ func (s *State) Cleanup() error { } // initTemplates loads all template folders into the state object. -func (s *State) initTemplates() error { +// The only reason it returns lazyTemplates is because we want to debug +// log it in a single JSON structure. +func (s *State) initTemplates() ([]lazyTemplate, error) { var err error basePath, err := getBasePath(s.Config.BaseDir) if err != nil { - return err + return nil, err } templates, err := findTemplates(basePath, templatesDirectory) if err != nil { - return err + return nil, err } testTemplates, err := findTemplates(basePath, templatesTestDirectory) if err != nil { - return err + return nil, err } for k, v := range testTemplates { @@ -180,7 +203,7 @@ func (s *State) initTemplates() error { driverTemplates, err := s.Driver.Templates() if err != nil { - return err + return nil, err } for template, contents := range driverTemplates { @@ -190,14 +213,14 @@ func (s *State) initTemplates() error { for _, replace := range s.Config.Replacements { splits := strings.Split(replace, ";") if len(splits) != 2 { - return errors.Errorf("replace parameters must have 2 arguments, given: %s", replace) + return nil, errors.Errorf("replace parameters must have 2 arguments, given: %s", replace) } original, replacement := splits[0], splits[1] _, ok := templates[original] if !ok { - return errors.Errorf("replace can only replace existing templates, %s does not exist", original) + return nil, errors.Errorf("replace can only replace existing templates, %s does not exist", original) } templates[original] = fileLoader(replacement) @@ -218,38 +241,29 @@ func (s *State) initTemplates() error { }) } - if s.Config.Debug { - b, err := json.Marshal(lazyTemplates) - if err != nil { - return errors.Wrap(err, "unable to json marshal template list") - } - - fmt.Printf("%s\n", b) - } - s.Templates, err = loadTemplates(lazyTemplates, templatesDirectory) if err != nil { - return err + return nil, err } s.SingletonTemplates, err = loadTemplates(lazyTemplates, templatesSingletonDirectory) if err != nil { - return err + return nil, err } if !s.Config.NoTests { s.TestTemplates, err = loadTemplates(lazyTemplates, templatesTestDirectory) if err != nil { - return err + return nil, err } s.SingletonTestTemplates, err = loadTemplates(lazyTemplates, templatesSingletonTestDirectory) if err != nil { - return err + return nil, err } } - return nil + return lazyTemplates, nil } // findTemplates uses a base path: (/home/user/gopath/src/../sqlboiler/) @@ -440,8 +454,16 @@ func columnMerge(dst, src drivers.Column) drivers.Column { return ret } -// Tags must be in a format like: json, xml, etc. -var rgxValidTag = regexp.MustCompile(`[a-zA-Z_\.]+`) +// initOutFolder creates the folder that will hold the generated output. +func (s *State) initOutFolder() error { + if s.Config.Wipe { + if err := os.RemoveAll(s.Config.OutFolder); err != nil { + return err + } + } + + return os.MkdirAll(s.Config.OutFolder, os.ModePerm) +} // initTags removes duplicate tags and validates the format // of all user tags are simple strings without quotes: [a-zA-Z_\.]+ @@ -456,15 +478,10 @@ func (s *State) initTags(tags []string) error { return nil } -// initOutFolder creates the folder that will hold the generated output. -func (s *State) initOutFolder() error { - if s.Config.Wipe { - if err := os.RemoveAll(s.Config.OutFolder); err != nil { - return err - } - } +func (s *State) initAliases(a Aliases) error { + FillAliases(&a, s.Tables) - return os.MkdirAll(s.Config.OutFolder, os.ModePerm) + return nil } // checkPKeys ensures every table has a primary key column diff --git a/boilingcore/config.go b/boilingcore/config.go index 84f1f8e60..ec3e48196 100644 --- a/boilingcore/config.go +++ b/boilingcore/config.go @@ -7,35 +7,104 @@ import ( // Config for the running of the commands type Config struct { - DriverName string - DriverConfig drivers.Config - - PkgName string - OutFolder string - BaseDir string - Tags []string - Replacements []string - Debug bool - AddGlobal bool - AddPanic bool - NoContext bool - NoTests bool - NoHooks bool - NoAutoTimestamps bool - NoRowsAffected bool - Wipe bool - StructTagCasing string - - Imports importers.Collection - - TypeReplaces []TypeReplace `toml:"types"` + DriverName string `toml:"driver_name,omitempty" json:"driver_name,omitempty"` + DriverConfig drivers.Config `toml:"driver_config,omitempty" json:"driver_config,omitempty"` + + PkgName string `toml:"pkg_name,omitempty" json:"pkg_name,omitempty"` + OutFolder string `toml:"out_folder,omitempty" json:"out_folder,omitempty"` + BaseDir string `toml:"base_dir,omitempty" json:"base_dir,omitempty"` + Tags []string `toml:"tags,omitempty" json:"tags,omitempty"` + Replacements []string `toml:"replacements,omitempty" json:"replacements,omitempty"` + Debug bool `toml:"debug,omitempty" json:"debug,omitempty"` + AddGlobal bool `toml:"add_global,omitempty" json:"add_global,omitempty"` + AddPanic bool `toml:"add_panic,omitempty" json:"add_panic,omitempty"` + NoContext bool `toml:"no_context,omitempty" json:"no_context,omitempty"` + NoTests bool `toml:"no_tests,omitempty" json:"no_tests,omitempty"` + NoHooks bool `toml:"no_hooks,omitempty" json:"no_hooks,omitempty"` + NoAutoTimestamps bool `toml:"no_auto_timestamps,omitempty" json:"no_auto_timestamps,omitempty"` + NoRowsAffected bool `toml:"no_rows_affected,omitempty" json:"no_rows_affected,omitempty"` + Wipe bool `toml:"wipe,omitempty" json:"wipe,omitempty"` + StructTagCasing string `toml:"struct_tag_casing,omitempty" json:"struct_tag_casing,omitempty"` + + Imports importers.Collection `toml:"imports,omitempty" json:"imports,omitempty"` + + Aliases Aliases `toml:"aliases,omitempty" json:"aliases,omitempty"` + TypeReplaces []TypeReplace `toml:"type_replaces,omitempty" json:"type_replaces,omitempty"` } // TypeReplace replaces a column type with something else type TypeReplace struct { - Match drivers.Column `toml:"match"` - Replace drivers.Column `toml:"replace"` - Imports importers.Set `toml:"imports"` + Match drivers.Column `toml:"match,omitempty" json:"match,omitempty"` + Replace drivers.Column `toml:"replace,omitempty" json:"replace,omitempty"` + Imports importers.Set `toml:"imports,omitempty" json:"imports,omitempty"` +} + +// ConvertAliases is necessary because viper +func ConvertAliases(i interface{}) (a Aliases) { + if i == nil { + return a + } + + topLevel := i.(map[string]interface{}) + tables := topLevel["tables"].(map[string]interface{}) + + for name, tIntf := range tables { + if a.Tables == nil { + a.Tables = make(map[string]TableAlias) + } + + t := tIntf.(map[string]interface{}) + + var ta TableAlias + + if s := t["up_plural"]; s != nil { + ta.UpPlural = s.(string) + } + if s := t["up_singular"]; s != nil { + ta.UpSingular = s.(string) + } + if s := t["down_plural"]; s != nil { + ta.DownPlural = s.(string) + } + if s := t["down_singular"]; s != nil { + ta.DownSingular = s.(string) + } + + if colsIntf, ok := t["columns"]; ok { + cols := colsIntf.(map[string]interface{}) + ta.Columns = make(map[string]string) + for k, v := range cols { + ta.Columns[k] = v.(string) + } + } + + a.Tables[name] = ta + } + relationships := topLevel["relationships"].(map[string]interface{}) + + for name, rIntf := range relationships { + if a.Relationships == nil { + a.Relationships = make(map[string]RelationshipAlias) + } + + var ra RelationshipAlias + rel := rIntf.(map[string]interface{}) + + if s := rel["local"]; s != nil { + ra.Local = s.(string) + } + if s := rel["foreign"]; s != nil { + ra.Foreign = s.(string) + } + + if len(ra.Foreign) == 0 || len(ra.Local) == 0 { + panic("when defining a relationship alias, must name both sides of relationship") + } + + a.Relationships[name] = ra + } + + return a } // ConvertTypeReplace is necessary because viper diff --git a/boilingcore/config_test.go b/boilingcore/config_test.go new file mode 100644 index 000000000..b83626d6d --- /dev/null +++ b/boilingcore/config_test.go @@ -0,0 +1,146 @@ +package boilingcore + +import ( + "testing" + + "github.com/volatiletech/sqlboiler/drivers" +) + +func TestConvertAliases(t *testing.T) { + t.Parallel() + + var intf interface{} = map[string]interface{}{ + "tables": map[string]interface{}{ + "table_name": map[string]interface{}{ + "up_plural": "a", + "up_singular": "b", + "down_plural": "c", + "down_singular": "d", + + "columns": map[string]interface{}{ + "a": "b", + }, + }, + }, + "relationships": map[string]interface{}{ + "ib_fk_1": map[string]interface{}{ + "local": "a", + "foreign": "b", + }, + }, + } + + aliases := ConvertAliases(intf) + + if len(aliases.Tables) != 1 { + t.Fatal("should have one table alias") + } + + table := aliases.Tables["table_name"] + if table.UpPlural != "a" { + t.Error("value was wrong:", table.UpPlural) + } + if table.UpSingular != "b" { + t.Error("value was wrong:", table.UpSingular) + } + if table.DownPlural != "c" { + t.Error("value was wrong:", table.DownPlural) + } + if table.DownSingular != "d" { + t.Error("value was wrong:", table.DownSingular) + } + + if len(table.Columns) != 1 { + t.Error("should have one column") + } + + if table.Columns["a"] != "b" { + t.Error("column alias was wrong") + } + + if len(aliases.Tables) != 1 { + t.Fatal("should have one relationship alias") + } + + rel := aliases.Relationships["ib_fk_1"] + if rel.Local != "a" { + t.Error("value was wrong:", rel.Local) + } + if rel.Foreign != "b" { + t.Error("value was wrong:", rel.Foreign) + } +} + +func TestConvertTypeReplace(t *testing.T) { + t.Parallel() + + fullColumn := map[string]interface{}{ + "name": "a", + "type": "b", + "db_type": "c", + "udt_name": "d", + "full_db_type": "e", + "arr_type": "f", + "auto_generated": true, + "nullable": true, + } + + var intf interface{} = []interface{}{ + map[string]interface{}{ + "match": fullColumn, + "replace": fullColumn, + "imports": map[string]interface{}{ + "standard": []interface{}{ + "abc", + }, + "third_party": []interface{}{ + "github.com/abc", + }, + }, + }, + } + + typeReplace := ConvertTypeReplace(intf) + if len(typeReplace) != 1 { + t.Error("should have one entry") + } + + checkColumn := func(t *testing.T, c drivers.Column) { + t.Helper() + if c.Name != "a" { + t.Error("value was wrong:", c.Name) + } + if c.Type != "b" { + t.Error("value was wrong:", c.Type) + } + if c.DBType != "c" { + t.Error("value was wrong:", c.DBType) + } + if c.UDTName != "d" { + t.Error("value was wrong:", c.UDTName) + } + if c.FullDBType != "e" { + t.Error("value was wrong:", c.FullDBType) + } + if *c.ArrType != "f" { + t.Error("value was wrong:", c.ArrType) + } + if c.AutoGenerated != true { + t.Error("value was wrong:", c.AutoGenerated) + } + if c.Nullable != true { + t.Error("value was wrong:", c.Nullable) + } + } + + r := typeReplace[0] + checkColumn(t, r.Match) + checkColumn(t, r.Replace) + + if got := r.Imports.Standard[0]; got != "abc" { + t.Error("standard import wrong:", got) + } + if got := r.Imports.ThirdParty[0]; got != "github.com/abc" { + t.Error("standard import wrong:", got) + } +} diff --git a/boilingcore/templates.go b/boilingcore/templates.go index 181e27dac..e733ad260 100644 --- a/boilingcore/templates.go +++ b/boilingcore/templates.go @@ -16,8 +16,9 @@ import ( // templateData for sqlboiler templates type templateData struct { - Tables []drivers.Table - Table drivers.Table + Tables []drivers.Table + Table drivers.Table + Aliases Aliases // Controls what names are output PkgName string diff --git a/boilingcore/text_helpers.go b/boilingcore/text_helpers.go index 4f5b3b7f0..d17d1274d 100644 --- a/boilingcore/text_helpers.go +++ b/boilingcore/text_helpers.go @@ -202,29 +202,23 @@ func txtNameToOne(fk drivers.ForeignKey) (localFn, foreignFn string) { // fk == table = industry.Industries // fk != table = industry.MappedIndustryIndustry func txtNameToMany(toMany drivers.ToManyRelationship) (localFn, foreignFn string) { - if toMany.ToJoinTable { - localFkey := strmangle.Singular(trimSuffixes(toMany.JoinLocalColumn)) - foreignFkey := strmangle.Singular(trimSuffixes(toMany.JoinForeignColumn)) + if !toMany.ToJoinTable { + panic(fmt.Sprintf("this method is only for join tables: %s <-> %s, %s", toMany.Table, toMany.ForeignTable, toMany.Name)) + } - if localFkey != strmangle.Singular(toMany.Table) { - foreignFn = strmangle.TitleCase(localFkey) - } - foreignFn += strmangle.TitleCase(strmangle.Plural(toMany.Table)) + localFkey := strmangle.Singular(trimSuffixes(toMany.JoinLocalColumn)) + foreignFkey := strmangle.Singular(trimSuffixes(toMany.JoinForeignColumn)) - if foreignFkey != strmangle.Singular(toMany.ForeignTable) { - localFn = strmangle.TitleCase(foreignFkey) - } - localFn += strmangle.TitleCase(strmangle.Plural(toMany.ForeignTable)) - - return localFn, foreignFn + if localFkey != strmangle.Singular(toMany.Table) { + foreignFn = strmangle.TitleCase(localFkey) } + foreignFn += strmangle.TitleCase(strmangle.Plural(toMany.Table)) - fkeyName := strmangle.Singular(trimSuffixes(toMany.ForeignColumn)) - if fkeyName != strmangle.Singular(toMany.Table) { - localFn = strmangle.TitleCase(fkeyName) + if foreignFkey != strmangle.Singular(toMany.ForeignTable) { + localFn = strmangle.TitleCase(foreignFkey) } localFn += strmangle.TitleCase(strmangle.Plural(toMany.ForeignTable)) - foreignFn = strmangle.TitleCase(strmangle.Singular(fkeyName)) + return localFn, foreignFn } diff --git a/boilingcore/text_helpers_test.go b/boilingcore/text_helpers_test.go index 2061e1aed..2ac7c267d 100644 --- a/boilingcore/text_helpers_test.go +++ b/boilingcore/text_helpers_test.go @@ -107,7 +107,7 @@ func TestTxtsFromOneToOne(t *testing.T) { } } -func TestTxtsFromMany(t *testing.T) { +/*func TestTxtsFromMany(t *testing.T) { t.Parallel() tables, err := drivers.Tables(&mocks.MockDriver{}, "public", nil, nil) @@ -154,6 +154,7 @@ func TestTxtsFromMany(t *testing.T) { t.Errorf("Want:\n%s\nGot:\n%s\n", spew.Sdump(expect), spew.Sdump(texts)) } } +*/ func TestTxtNameToOne(t *testing.T) { t.Parallel() @@ -219,11 +220,12 @@ func TestTxtNameToMany(t *testing.T) { LocalFn string ForeignFn string }{ - {"airports", "id", "jets", "airport_id", false, "", "", "Jets", "Airport"}, + /*{"airports", "id", "jets", "airport_id", false, "", "", "Jets", "Airport"}, {"airports", "id", "jets", "holiday_airport_id", false, "", "", "HolidayAirportJets", "HolidayAirport"}, {"jets", "id", "jets", "jet_id", false, "", "", "Jets", "Jet"}, {"jets", "id", "jets", "plane_id", false, "", "", "PlaneJets", "Plane"}, + {"race_results", "id", "race_result_scratchings", "results_id", false, "", "", "ResultRaceResultScratchings", "Result"},*/ {"pilots", "id", "languages", "id", true, "pilot_id", "language_id", "Languages", "Pilots"}, {"pilots", "id", "languages", "id", true, "captain_id", "lingo_id", "LingoLanguages", "CaptainPilots"}, @@ -231,8 +233,6 @@ func TestTxtNameToMany(t *testing.T) { {"pilots", "id", "pilots", "id", true, "pilot_id", "mentor_id", "MentorPilots", "Pilots"}, {"pilots", "id", "pilots", "id", true, "mentor_id", "pilot_id", "Pilots", "MentorPilots"}, {"pilots", "id", "pilots", "id", true, "captain_id", "mentor_id", "MentorPilots", "CaptainPilots"}, - - {"race_results", "id", "race_result_scratchings", "results_id", false, "", "", "ResultRaceResultScratchings", "Result"}, } for i, test := range tests { diff --git a/drivers/relationships.go b/drivers/relationships.go index 19decbddf..6a18c4371 100644 --- a/drivers/relationships.go +++ b/drivers/relationships.go @@ -5,6 +5,8 @@ package drivers // local table, that column can also be unique which changes the dynamic into a // one-to-one style, not a to-many. type ToOneRelationship struct { + Name string `json:"name"` + Table string `json:"table"` Column string `json:"column"` Nullable bool `json:"nullable"` @@ -20,6 +22,8 @@ type ToOneRelationship struct { // local table has no id, and the foreign table has an id that matches a column // in the local table. type ToManyRelationship struct { + Name string `json:"name"` + Table string `json:"table"` Column string `json:"column"` Nullable bool `json:"nullable"` @@ -33,10 +37,12 @@ type ToManyRelationship struct { ToJoinTable bool `json:"to_join_table"` JoinTable string `json:"join_table"` + JoinLocalFKeyName string `json:"join_local_fkey_name"` JoinLocalColumn string `json:"join_local_column"` JoinLocalColumnNullable bool `json:"join_local_column_nullable"` JoinLocalColumnUnique bool `json:"join_local_column_unique"` + JoinForeignFKeyName string `json:"join_foreign_fkey_name"` JoinForeignColumn string `json:"join_foreign_column"` JoinForeignColumnNullable bool `json:"join_foreign_column_nullable"` JoinForeignColumnUnique bool `json:"join_foreign_column_unique"` @@ -87,6 +93,7 @@ func toManyRelationships(table Table, tables []Table) []ToManyRelationship { func buildToOneRelationship(localTable Table, foreignKey ForeignKey, foreignTable Table, tables []Table) ToOneRelationship { return ToOneRelationship{ + Name: foreignKey.Name, Table: localTable.Name, Column: foreignKey.ForeignColumn, Nullable: foreignKey.ForeignColumnNullable, @@ -102,6 +109,7 @@ func buildToOneRelationship(localTable Table, foreignKey ForeignKey, foreignTabl func buildToManyRelationship(localTable Table, foreignKey ForeignKey, foreignTable Table, tables []Table) ToManyRelationship { if !foreignTable.IsJoinTable { return ToManyRelationship{ + Name: foreignKey.Name, Table: localTable.Name, Column: foreignKey.ForeignColumn, Nullable: foreignKey.ForeignColumnNullable, @@ -123,6 +131,7 @@ func buildToManyRelationship(localTable Table, foreignKey ForeignKey, foreignTab ToJoinTable: true, JoinTable: foreignTable.Name, + JoinLocalFKeyName: foreignKey.Name, JoinLocalColumn: foreignKey.Column, JoinLocalColumnNullable: foreignKey.Nullable, JoinLocalColumnUnique: foreignKey.Unique, @@ -133,6 +142,7 @@ func buildToManyRelationship(localTable Table, foreignKey ForeignKey, foreignTab continue } + relationship.JoinForeignFKeyName = fk.Name relationship.JoinForeignColumn = fk.Column relationship.JoinForeignColumnNullable = fk.Nullable relationship.JoinForeignColumnUnique = fk.Unique diff --git a/drivers/relationships_test.go b/drivers/relationships_test.go index 59543d982..0e5252f01 100644 --- a/drivers/relationships_test.go +++ b/drivers/relationships_test.go @@ -54,6 +54,7 @@ func TestToOneRelationships(t *testing.T) { expected := []ToOneRelationship{ { + Name: "jets_pilot_id_fk", Table: "pilots", Column: "id", Nullable: false, @@ -65,6 +66,7 @@ func TestToOneRelationships(t *testing.T) { ForeignColumnUnique: true, }, { + Name: "licenses_pilot_id_fk", Table: "pilots", Column: "id", Nullable: false, @@ -138,6 +140,7 @@ func TestToManyRelationships(t *testing.T) { expected := []ToManyRelationship{ { + Name: "jets_pilot_id_fk", Table: "pilots", Column: "id", Nullable: false, @@ -151,6 +154,7 @@ func TestToManyRelationships(t *testing.T) { ToJoinTable: false, }, { + Name: "licenses_pilot_id_fk", Table: "pilots", Column: "id", Nullable: false, @@ -177,10 +181,12 @@ func TestToManyRelationships(t *testing.T) { ToJoinTable: true, JoinTable: "pilot_languages", + JoinLocalFKeyName: "pilot_id_fk", JoinLocalColumn: "pilot_id", JoinLocalColumnNullable: false, JoinLocalColumnUnique: false, + JoinForeignFKeyName: "language_id_fk", JoinForeignColumn: "language_id", JoinForeignColumnNullable: false, JoinForeignColumnUnique: false, @@ -250,6 +256,7 @@ func TestToManyRelationshipsNull(t *testing.T) { expected := []ToManyRelationship{ { + Name: "jets_pilot_id_fk", Table: "pilots", Column: "id", Nullable: true, @@ -263,6 +270,7 @@ func TestToManyRelationshipsNull(t *testing.T) { ToJoinTable: false, }, { + Name: "licenses_pilot_id_fk", Table: "pilots", Column: "id", Nullable: true, @@ -289,10 +297,12 @@ func TestToManyRelationshipsNull(t *testing.T) { ToJoinTable: true, JoinTable: "pilot_languages", + JoinLocalFKeyName: "pilot_id_fk", JoinLocalColumn: "pilot_id", JoinLocalColumnNullable: true, JoinLocalColumnUnique: false, + JoinForeignFKeyName: "language_id_fk", JoinForeignColumn: "language_id", JoinForeignColumnNullable: true, JoinForeignColumnUnique: false, diff --git a/main.go b/main.go index d6da1f9cc..99fb864fe 100644 --- a/main.go +++ b/main.go @@ -174,6 +174,7 @@ func preRun(cmd *cobra.Command, args []string) error { StructTagCasing: strings.ToLower(viper.GetString("struct-tag-casing")), // camel | snake Tags: viper.GetStringSlice("tag"), Replacements: viper.GetStringSlice("replace"), + Aliases: boilingcore.ConvertAliases(viper.Get("aliases")), TypeReplaces: boilingcore.ConvertTypeReplace(viper.Get("types")), }