From 389d6f57e3a56b2455b47cb8e81ca5f225dc1b13 Mon Sep 17 00:00:00 2001 From: bokwoon Date: Fri, 9 Jun 2023 21:18:44 +0800 Subject: [PATCH] tables: add flag to include schema in struct names Fixes #4. --- ddl/integration_test.go | 23 +++ ddl/tables_cmd.go | 49 ++++- .../postgres/schema_qualified_tables.go | 185 ++++++++++++++++++ 3 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 ddl/testdata/postgres/schema_qualified_tables.go diff --git a/ddl/integration_test.go b/ddl/integration_test.go index 0911ebd..a51e0cd 100644 --- a/ddl/integration_test.go +++ b/ddl/integration_test.go @@ -70,6 +70,7 @@ func TestPostgres(t *testing.T) { return record[:len(record)-1] }, }) + testTablesCmdSchemaQualifiedStructs(t, dialect, *postgresDSN, "testdata/postgres/schema_qualified_tables.go") } func TestMySQL(t *testing.T) { @@ -392,6 +393,28 @@ func testLoadDump(t *testing.T, dialect string, dsn string, transforms map[strin assertCSVsAreIdentical(t, tempDir, "testdata/extended_subset", nil, transforms) } +func testTablesCmdSchemaQualifiedStructs(t *testing.T, dialect string, dsn string, goldenFile string) { + tablesCmd, err := TablesCommand("-db", dsn, "-schema-qualified-structs") + if err != nil { + t.Fatal(testutil.Callers(), err) + } + var buf bytes.Buffer + tablesCmd.Stdout = &buf + err = tablesCmd.Run() + if err != nil { + t.Fatal(testutil.Callers(), err) + } + b, err := os.ReadFile(goldenFile) + if err != nil { + t.Fatal(testutil.Callers(), err) + } + gotOutput := buf.String() + wantOutput := string(b) + if diff := testutil.Diff(gotOutput, wantOutput); diff != "" { + t.Error(testutil.Callers(), diff) + } +} + func rewriteCSV(filename string, transform func(record []string) []string) error { file, err := os.Open(filename) if err != nil { diff --git a/ddl/tables_cmd.go b/ddl/tables_cmd.go index 80e045a..cfe36b5 100644 --- a/ddl/tables_cmd.go +++ b/ddl/tables_cmd.go @@ -27,6 +27,11 @@ type TablesCmd struct { // blank to write to Stdout instead. Filename string + // SchemaQualifiedStructs controls whether the generated struct names are + // schema qualified e.g. `type SCHEMA_TABLE struct` instead of `type TABLE + // struct`. + SchemaQualifiedStructs bool + // Stdout specifies the command's standard out to write to if no Filename // is provided. If nil, the command writes to os.Stdout. Stdout io.Writer @@ -58,6 +63,7 @@ func TablesCommand(args ...string) (*TablesCmd, error) { flagset.StringVar(&cmd.HistoryTable, "history-table", "sqddl_history", "Name of migration history table.") flagset.StringVar(&cmd.PackageName, "pkg", "_", "Package name used in the generated code.") flagset.StringVar(&cmd.Filename, "file", "", "Name of the file to write the generated code into. Leave blank to write to stdout.") + flagset.BoolVar(&cmd.SchemaQualifiedStructs, "schema-qualified-structs", false, "Schema-qualify the generated struct names.") flagset.StringVar(&schemas, "schemas", "", "Comma-separated list of schemas to be included.") flagset.StringVar(&excludeSchemas, "exclude-schemas", "", "Comma-separated list of schemas to be excluded.") flagset.StringVar(&tables, "tables", "", "Comma-separated list of tables to be included.") @@ -120,15 +126,46 @@ func (cmd *TablesCmd) Run() error { defer cmd.DB.Close() } - tableStructs, err := NewTableStructs(cmd.Dialect, cmd.DB, Filter{ - Schemas: cmd.Schemas, - ExcludeSchemas: cmd.ExcludeSchemas, - Tables: cmd.Tables, - ExcludeTables: append(cmd.ExcludeTables, cmd.HistoryTable), - }) + var tableStructs TableStructs + var catalog Catalog + dbi := &DatabaseIntrospector{ + Dialect: cmd.Dialect, + DB: cmd.DB, + Filter: Filter{ + Schemas: cmd.Schemas, + ExcludeSchemas: cmd.ExcludeSchemas, + Tables: cmd.Tables, + ExcludeTables: append(cmd.ExcludeTables, cmd.HistoryTable), + }, + } + dbi.ObjectTypes = []string{"TABLES"} + err := dbi.WriteCatalog(&catalog) + if err != nil { + return err + } + err = tableStructs.ReadCatalog(&catalog) if err != nil { return err } + if cmd.SchemaQualifiedStructs { + for i := range tableStructs { + tableStruct := &tableStructs[i] + tableName := strings.ToLower(tableStruct.Name) + if tableStruct.Fields[0].NameTag != "" { + tableName = tableStruct.Fields[0].NameTag + } + tableSchema, tableName, _ := strings.Cut(tableName, ".") + if tableName == "" { + tableSchema, tableName = tableName, tableSchema + } + if tableSchema == "" && catalog.CurrentSchema != "" { + tableSchema = catalog.CurrentSchema + } + if tableSchema != "" { + tableStruct.Name = strings.ToUpper(strings.ReplaceAll(tableSchema+"_"+tableName, " ", "_")) + } + } + } text, err := tableStructs.MarshalText() if err != nil { return err diff --git a/ddl/testdata/postgres/schema_qualified_tables.go b/ddl/testdata/postgres/schema_qualified_tables.go new file mode 100644 index 0000000..812b6f7 --- /dev/null +++ b/ddl/testdata/postgres/schema_qualified_tables.go @@ -0,0 +1,185 @@ +package _ + +import "github.com/bokwoon95/sq" + +type PUBLIC_ACTOR struct { + sq.TableStruct + ACTOR_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + FIRST_NAME sq.StringField `ddl:"type=varchar(45) notnull"` + LAST_NAME sq.StringField `ddl:"type=varchar(45) notnull index"` + FULL_NAME sq.StringField `ddl:"type=text generated"` + FULL_NAME_REVERSED sq.StringField `ddl:"type=text generated"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_ADDRESS struct { + sq.TableStruct + ADDRESS_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + ADDRESS sq.StringField `ddl:"type=varchar(50) notnull"` + ADDRESS2 sq.StringField `ddl:"type=varchar(50)"` + DISTRICT sq.StringField `ddl:"type=varchar(20) notnull"` + CITY_ID sq.NumberField `ddl:"type=int notnull references={city onupdate=cascade ondelete=restrict deferrable index}"` + POSTAL_CODE sq.StringField `ddl:"type=varchar(10)"` + PHONE sq.StringField `ddl:"type=text notnull"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_CATEGORY struct { + sq.TableStruct + CATEGORY_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + NAME sq.StringField `ddl:"type=varchar(45) notnull"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_CITY struct { + sq.TableStruct + CITY_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + CITY sq.StringField `ddl:"type=varchar(50) notnull"` + COUNTRY_ID sq.NumberField `ddl:"type=int notnull references={country onupdate=cascade ondelete=restrict deferrable index}"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_COUNTRY struct { + sq.TableStruct + COUNTRY_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + COUNTRY sq.StringField `ddl:"type=varchar(50) notnull"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_CUSTOMER struct { + sq.TableStruct + CUSTOMER_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + STORE_ID sq.NumberField `ddl:"type=int notnull references={store onupdate=cascade ondelete=restrict deferrable index}"` + FIRST_NAME sq.StringField `ddl:"type=varchar(45) notnull"` + LAST_NAME sq.StringField `ddl:"type=varchar(45) notnull index"` + EMAIL sq.StringField `ddl:"type=varchar(50) unique"` + ADDRESS_ID sq.NumberField `ddl:"type=int notnull references={address onupdate=cascade ondelete=restrict deferrable index}"` + ACTIVE sq.BooleanField `ddl:"type=boolean notnull default=true"` + CREATE_DATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` + _ struct{} `ddl:"unique=email,first_name,last_name"` +} + +type PUBLIC_DEPARTMENT struct { + sq.TableStruct + DEPARTMENT_ID sq.UUIDField `ddl:"type=uuid notnull primarykey"` + NAME sq.StringField `ddl:"type=varchar(255) notnull"` +} + +type PUBLIC_EMPLOYEE struct { + sq.TableStruct + EMPLOYEE_ID sq.UUIDField `ddl:"type=uuid notnull primarykey"` + NAME sq.StringField `ddl:"type=varchar(255) notnull"` + TITLE sq.StringField `ddl:"type=varchar(255) notnull"` + MANAGER_ID sq.UUIDField `ddl:"type=uuid references={employee.employee_id index}"` +} + +type PUBLIC_EMPLOYEE_DEPARTMENT struct { + sq.TableStruct `ddl:"primarykey=employee_id,department_id"` + EMPLOYEE_ID sq.UUIDField `ddl:"type=uuid notnull references={employee index}"` + DEPARTMENT_ID sq.UUIDField `ddl:"type=uuid notnull references={department index}"` +} + +type PUBLIC_FILM struct { + sq.TableStruct + FILM_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + TITLE sq.StringField `ddl:"type=text notnull index"` + DESCRIPTION sq.StringField `ddl:"type=text"` + RELEASE_YEAR sq.NumberField `ddl:"type=year"` + LANGUAGE_ID sq.NumberField `ddl:"type=int notnull references={language onupdate=cascade ondelete=restrict deferrable index}"` + ORIGINAL_LANGUAGE_ID sq.NumberField `ddl:"type=int references={language.language_id onupdate=cascade ondelete=restrict deferrable index}"` + RENTAL_DURATION sq.NumberField `ddl:"type=int notnull default=3"` + RENTAL_RATE sq.NumberField `ddl:"type=numeric(4,2) notnull default=4.99"` + LENGTH sq.NumberField `ddl:"type=int"` + REPLACEMENT_COST sq.NumberField `ddl:"type=numeric(5,2) notnull default=19.99"` + RATING sq.EnumField `ddl:"type=mpaa_rating default='G'::mpaa_rating"` + SPECIAL_FEATURES sq.ArrayField `ddl:"type=text[]"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` + FULLTEXT sq.AnyField `ddl:"type=tsvector index={. using=gin}"` +} + +type PUBLIC_FILM_ACTOR struct { + sq.TableStruct `ddl:"primarykey=actor_id,film_id"` + ACTOR_ID sq.NumberField `ddl:"type=int notnull references={actor onupdate=cascade ondelete=restrict deferrable}"` + FILM_ID sq.NumberField `ddl:"type=int notnull references={film onupdate=cascade ondelete=restrict deferrable index}"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_FILM_CATEGORY struct { + sq.TableStruct `ddl:"primarykey=film_id,category_id"` + FILM_ID sq.NumberField `ddl:"type=int notnull references={film onupdate=cascade ondelete=restrict deferrable}"` + CATEGORY_ID sq.NumberField `ddl:"type=int notnull references={category onupdate=cascade ondelete=restrict deferrable}"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_INVENTORY struct { + sq.TableStruct + INVENTORY_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + FILM_ID sq.NumberField `ddl:"type=int notnull references={film onupdate=cascade ondelete=restrict deferrable index}"` + STORE_ID sq.NumberField `ddl:"type=int notnull references={store onupdate=cascade ondelete=restrict deferrable}"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` + _ struct{} `ddl:"index=store_id,film_id"` +} + +type PUBLIC_LANGUAGE struct { + sq.TableStruct + LANGUAGE_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + NAME sq.StringField `ddl:"type=text notnull"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_PAYMENT struct { + sq.TableStruct + PAYMENT_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + CUSTOMER_ID sq.NumberField `ddl:"type=int notnull references={customer onupdate=cascade ondelete=restrict deferrable index}"` + STAFF_ID sq.NumberField `ddl:"type=int notnull references={staff onupdate=cascade ondelete=restrict deferrable index}"` + RENTAL_ID sq.NumberField `ddl:"type=int references={rental onupdate=cascade ondelete=setnull deferrable index}"` + AMOUNT sq.NumberField `ddl:"type=numeric(5,2) notnull"` + PAYMENT_DATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_RENTAL struct { + sq.TableStruct + RENTAL_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + RENTAL_DATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` + INVENTORY_ID sq.NumberField `ddl:"type=int notnull references={inventory onupdate=cascade ondelete=restrict deferrable index}"` + CUSTOMER_ID sq.NumberField `ddl:"type=int notnull references={customer onupdate=cascade ondelete=restrict deferrable index}"` + RETURN_DATE sq.TimeField `ddl:"type=timestamptz"` + STAFF_ID sq.NumberField `ddl:"type=int notnull references={staff onupdate=cascade ondelete=restrict deferrable index}"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` + _ struct{} `ddl:"index={inventory_id,customer_id,staff_id unique}"` +} + +type PUBLIC_STAFF struct { + sq.TableStruct + STAFF_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + FIRST_NAME sq.StringField `ddl:"type=varchar(45) notnull"` + LAST_NAME sq.StringField `ddl:"type=varchar(45) notnull"` + ADDRESS_ID sq.NumberField `ddl:"type=int notnull references={address onupdate=cascade ondelete=restrict deferrable index}"` + PICTURE sq.BinaryField `ddl:"type=bytea"` + EMAIL sq.StringField `ddl:"type=varchar(50) unique"` + STORE_ID sq.NumberField `ddl:"type=int references={store deferrable index}"` + ACTIVE sq.BooleanField `ddl:"type=boolean notnull default=true"` + USERNAME sq.StringField `ddl:"type=varchar(16) notnull"` + PASSWORD sq.StringField `ddl:"type=varchar(40)"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_STORE struct { + sq.TableStruct + STORE_ID sq.NumberField `ddl:"type=int notnull primarykey identity"` + MANAGER_STAFF_ID sq.NumberField `ddl:"type=int notnull references={staff.staff_id onupdate=cascade ondelete=restrict deferrable index}"` + ADDRESS_ID sq.NumberField `ddl:"type=int notnull references={address onupdate=cascade ondelete=restrict deferrable index}"` + LAST_UPDATE sq.TimeField `ddl:"type=timestamptz notnull default=CURRENT_TIMESTAMP"` +} + +type PUBLIC_TASK struct { + sq.TableStruct + TASK_ID sq.UUIDField `ddl:"type=uuid notnull primarykey"` + EMPLOYEE_ID sq.UUIDField `ddl:"type=uuid notnull"` + DEPARTMENT_ID sq.UUIDField `ddl:"type=uuid notnull"` + TASK sq.StringField `ddl:"type=varchar(255) notnull"` + DATA sq.JSONField `ddl:"type=jsonb"` + _ struct{} `ddl:"foreignkey={employee_id,department_id references=employee_department index}"` +}