diff --git a/CHANGELOG.md b/CHANGELOG.md index 367be78..5a39b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added a new field `QueryFolders` to `gen/drivers.DBInfo` for drivers to be able to include information about parsed queries. +- Added `gen/QueriesTemplates` which in the future will contain base templates for generating code for parsed qureries. +- Added a `QueryTemplate` field to `bobgen_helpers.Templates` for drivers to include additional templates for queries. +- Added a new reserved output key `queries`. This is handled specially for each query folder supplied by the driver. + ### Changed - Updated error constant generation to employ specific error types for making error matching easier. (thanks @mbezhanov) diff --git a/gen/bobgen-helpers/helpers.go b/gen/bobgen-helpers/helpers.go index b2f9680..4f005a0 100644 --- a/gen/bobgen-helpers/helpers.go +++ b/gen/bobgen-helpers/helpers.go @@ -35,6 +35,7 @@ func Version() string { type Templates struct { Models []fs.FS Factory []fs.FS + Queries []fs.FS } func DefaultOutputs(destination, pkgname string, noFactory bool, templates *Templates) []*gen.Output { @@ -57,6 +58,10 @@ func DefaultOutputs(destination, pkgname string, noFactory bool, templates *Temp PkgName: pkgname, Templates: append(templates.Models, gen.ModelTemplates), }, + { + Key: "queries", + Templates: append(templates.Queries, gen.QueriesTemplates), + }, } if !noFactory { diff --git a/gen/bobgen-mysql/driver/exclude-tables.golden.json b/gen/bobgen-mysql/driver/exclude-tables.golden.json index d8df1fb..674d03d 100644 --- a/gen/bobgen-mysql/driver/exclude-tables.golden.json +++ b/gen/bobgen-mysql/driver/exclude-tables.golden.json @@ -2339,6 +2339,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "TypeMonstersEnumNullable", diff --git a/gen/bobgen-mysql/driver/include-exclude-tables-mixed.golden.json b/gen/bobgen-mysql/driver/include-exclude-tables-mixed.golden.json index 00db9bf..1a0c00a 100644 --- a/gen/bobgen-mysql/driver/include-exclude-tables-mixed.golden.json +++ b/gen/bobgen-mysql/driver/include-exclude-tables-mixed.golden.json @@ -119,6 +119,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "github.com/go-sql-driver/mysql" diff --git a/gen/bobgen-mysql/driver/include-exclude-tables-regex.golden.json b/gen/bobgen-mysql/driver/include-exclude-tables-regex.golden.json index eab0389..0901873 100644 --- a/gen/bobgen-mysql/driver/include-exclude-tables-regex.golden.json +++ b/gen/bobgen-mysql/driver/include-exclude-tables-regex.golden.json @@ -119,6 +119,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "github.com/go-sql-driver/mysql" diff --git a/gen/bobgen-mysql/driver/include-exclude-tables.golden.json b/gen/bobgen-mysql/driver/include-exclude-tables.golden.json index 93e9b62..fe993d0 100644 --- a/gen/bobgen-mysql/driver/include-exclude-tables.golden.json +++ b/gen/bobgen-mysql/driver/include-exclude-tables.golden.json @@ -60,6 +60,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "github.com/go-sql-driver/mysql" diff --git a/gen/bobgen-mysql/driver/include-tables.golden.json b/gen/bobgen-mysql/driver/include-tables.golden.json index bd781e1..5afc194 100644 --- a/gen/bobgen-mysql/driver/include-tables.golden.json +++ b/gen/bobgen-mysql/driver/include-tables.golden.json @@ -119,6 +119,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "github.com/go-sql-driver/mysql" diff --git a/gen/bobgen-mysql/driver/mysql.golden.json b/gen/bobgen-mysql/driver/mysql.golden.json index b821b40..029a10b 100644 --- a/gen/bobgen-mysql/driver/mysql.golden.json +++ b/gen/bobgen-mysql/driver/mysql.golden.json @@ -2490,6 +2490,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "TypeMonstersEnumNullable", diff --git a/gen/bobgen-psql/driver/exclude-tables.golden.json b/gen/bobgen-psql/driver/exclude-tables.golden.json index d71d1f9..ff9421e 100644 --- a/gen/bobgen-psql/driver/exclude-tables.golden.json +++ b/gen/bobgen-psql/driver/exclude-tables.golden.json @@ -4578,6 +4578,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "UnicodeEnum", diff --git a/gen/bobgen-psql/driver/include-exclude-tables-mixed.golden.json b/gen/bobgen-psql/driver/include-exclude-tables-mixed.golden.json index a82f95e..c7fe6cf 100644 --- a/gen/bobgen-psql/driver/include-exclude-tables-mixed.golden.json +++ b/gen/bobgen-psql/driver/include-exclude-tables-mixed.golden.json @@ -133,6 +133,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "UnicodeEnum", diff --git a/gen/bobgen-psql/driver/include-exclude-tables-regex.golden.json b/gen/bobgen-psql/driver/include-exclude-tables-regex.golden.json index 6e24b79..0f28aca 100644 --- a/gen/bobgen-psql/driver/include-exclude-tables-regex.golden.json +++ b/gen/bobgen-psql/driver/include-exclude-tables-regex.golden.json @@ -133,6 +133,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "UnicodeEnum", diff --git a/gen/bobgen-psql/driver/include-exclude-tables.golden.json b/gen/bobgen-psql/driver/include-exclude-tables.golden.json index a122560..e8ab0a9 100644 --- a/gen/bobgen-psql/driver/include-exclude-tables.golden.json +++ b/gen/bobgen-psql/driver/include-exclude-tables.golden.json @@ -67,6 +67,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "UnicodeEnum", diff --git a/gen/bobgen-psql/driver/include-tables.golden.json b/gen/bobgen-psql/driver/include-tables.golden.json index 2fc32bf..4b35bef 100644 --- a/gen/bobgen-psql/driver/include-tables.golden.json +++ b/gen/bobgen-psql/driver/include-tables.golden.json @@ -133,6 +133,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "UnicodeEnum", diff --git a/gen/bobgen-psql/driver/psql.golden.json b/gen/bobgen-psql/driver/psql.golden.json index 217ac4f..cea4a83 100644 --- a/gen/bobgen-psql/driver/psql.golden.json +++ b/gen/bobgen-psql/driver/psql.golden.json @@ -4743,6 +4743,7 @@ "comment": "" } ], + "query_folders": null, "enums": [ { "Type": "UnicodeEnum", diff --git a/gen/bobgen-sqlite/driver/exclude-tables.golden.json b/gen/bobgen-sqlite/driver/exclude-tables.golden.json index 8087fdb..6ffbf2d 100644 --- a/gen/bobgen-sqlite/driver/exclude-tables.golden.json +++ b/gen/bobgen-sqlite/driver/exclude-tables.golden.json @@ -3093,6 +3093,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "modernc.org/sqlite" diff --git a/gen/bobgen-sqlite/driver/include-exclude-tables-mixed.golden.json b/gen/bobgen-sqlite/driver/include-exclude-tables-mixed.golden.json index e085298..6a1d8e9 100644 --- a/gen/bobgen-sqlite/driver/include-exclude-tables-mixed.golden.json +++ b/gen/bobgen-sqlite/driver/include-exclude-tables-mixed.golden.json @@ -245,6 +245,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "modernc.org/sqlite" diff --git a/gen/bobgen-sqlite/driver/include-exclude-tables-regex.golden.json b/gen/bobgen-sqlite/driver/include-exclude-tables-regex.golden.json index e6e4b6b..3ddf4d2 100644 --- a/gen/bobgen-sqlite/driver/include-exclude-tables-regex.golden.json +++ b/gen/bobgen-sqlite/driver/include-exclude-tables-regex.golden.json @@ -245,6 +245,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "modernc.org/sqlite" diff --git a/gen/bobgen-sqlite/driver/include-exclude-tables.golden.json b/gen/bobgen-sqlite/driver/include-exclude-tables.golden.json index af3da7e..104b6ea 100644 --- a/gen/bobgen-sqlite/driver/include-exclude-tables.golden.json +++ b/gen/bobgen-sqlite/driver/include-exclude-tables.golden.json @@ -123,6 +123,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "modernc.org/sqlite" diff --git a/gen/bobgen-sqlite/driver/include-tables.golden.json b/gen/bobgen-sqlite/driver/include-tables.golden.json index 7825914..f24b321 100644 --- a/gen/bobgen-sqlite/driver/include-tables.golden.json +++ b/gen/bobgen-sqlite/driver/include-tables.golden.json @@ -245,6 +245,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "modernc.org/sqlite" diff --git a/gen/bobgen-sqlite/driver/sqlite.golden.json b/gen/bobgen-sqlite/driver/sqlite.golden.json index b755c65..ab0d8c1 100644 --- a/gen/bobgen-sqlite/driver/sqlite.golden.json +++ b/gen/bobgen-sqlite/driver/sqlite.golden.json @@ -3403,6 +3403,7 @@ "comment": "" } ], + "query_folders": null, "enums": null, "extra_info": null, "driver_name": "modernc.org/sqlite" diff --git a/gen/drivers/interface.go b/gen/drivers/interface.go index 608947a..c9e93e6 100644 --- a/gen/drivers/interface.go +++ b/gen/drivers/interface.go @@ -54,9 +54,10 @@ type Types map[string]Type // DBInfo is the database's table data and dialect. type DBInfo[DBExtra, ConstraintExtra, IndexExtra any] struct { - Tables []Table[ConstraintExtra, IndexExtra] `json:"tables"` - Enums []Enum `json:"enums"` - ExtraInfo DBExtra `json:"extra_info"` + Tables Tables[ConstraintExtra, IndexExtra] `json:"tables"` + QueryFolders []QueryFolder `json:"query_folders"` + Enums []Enum `json:"enums"` + ExtraInfo DBExtra `json:"extra_info"` // DriverName is the module name of the underlying `database/sql` driver DriverName string `json:"driver_name"` } diff --git a/gen/drivers/query.go b/gen/drivers/query.go new file mode 100644 index 0000000..78a9a92 --- /dev/null +++ b/gen/drivers/query.go @@ -0,0 +1,53 @@ +package drivers + +type QueryFolder struct { + Path string + Files []QueryFile +} + +type QueryFile struct { + Path string + Queries []Query +} + +type Query struct { + Name string `yaml:"name"` + SQL string `yaml:"raw"` + RowName string `yaml:"row_name"` + GenerateRow bool `yaml:"generate_row"` + + Columns []QueryArg `yaml:"columns"` + Args []QueryArg `yaml:"args"` +} + +type QueryArg struct { + Name string `yaml:"name"` + Nullable bool `yaml:"nullable"` + TypeName string `yaml:"type"` + Refs []Ref `yaml:"refs"` +} + +type Ref struct { + Key string `yaml:"key"` + Column string `yaml:"column"` +} + +type db interface { + GetColumn(key string, col string) Column +} + +func (c QueryArg) Type(db db) string { + if len(c.Refs) == 0 { + return c.TypeName + } + + ref := db.GetColumn(c.Refs[0].Key, c.Refs[0].Column).Type + + for _, r := range c.Refs[1:] { + if ref != db.GetColumn(r.Key, r.Column).Type { + return c.TypeName + } + } + + return ref +} diff --git a/gen/gen.go b/gen/gen.go index c64ef50..ad2439a 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -120,6 +120,7 @@ func Run[T, C, I any](ctx context.Context, s *State[C], driver drivers.Interface data := &TemplateData[T, C, I]{ Dialect: driver.Dialect(), Tables: dbInfo.Tables, + QueryFolders: dbInfo.QueryFolders, Enums: dbInfo.Enums, ExtraInfo: dbInfo.ExtraInfo, Aliases: s.Config.Aliases, @@ -162,17 +163,14 @@ func generate[T, C, I any](s *State[C], data *TemplateData[T, C, I], goVersion s templateHeaderByteBuffer := &bytes.Buffer{} for _, o := range s.Outputs { - if len(o.Templates) == 0 { - continue - } - if _, ok := knownKeys[o.Key]; ok { return fmt.Errorf("Duplicate output key: %q", o.Key) } knownKeys[o.Key] = struct{}{} - // set the package name for this output - data.PkgName = o.PkgName + if len(o.Templates) == 0 { + continue + } if err := o.initTemplates(s.CustomTemplateFuncs); err != nil { return fmt.Errorf("unable to initialize templates: %w", err) @@ -182,26 +180,66 @@ func generate[T, C, I any](s *State[C], data *TemplateData[T, C, I], goVersion s continue } - if err := o.initOutFolders(s.Config.Wipe); err != nil { - return fmt.Errorf("unable to initialize the output folders: %w", err) - } + iterator := slices.Values([]struct{}{{}}) - // assign reusable scratch buffers to provided Output - o.templateByteBuffer = templateByteBuffer - o.templateHeaderByteBuffer = templateHeaderByteBuffer + if o.Key == "queries" { + iterator = func(yield func(struct{}) bool) { + for _, folder := range data.QueryFolders { + o.PkgName = filepath.Base(folder.Path) + o.OutFolder = folder.Path + data.QueryFolder = folder - if err := generateSingletonOutput(o, data, goVersion, s.Config.NoTests); err != nil { - return fmt.Errorf("singleton template output: %w", err) + if !yield(struct{}{}) { + return + } + } + } } - dirExtMap := groupTemplates(o.tableTemplates) + for range iterator { + // set the package name for this output + data.PkgName = o.PkgName + + if err := o.initOutFolders(s.Config.Wipe); err != nil { + return fmt.Errorf("unable to initialize the output folders: %w", err) + } + + // assign reusable scratch buffers to provided Output + o.templateByteBuffer = templateByteBuffer + o.templateHeaderByteBuffer = templateHeaderByteBuffer + + if err := generateSingletonOutput(o, data, goVersion, s.Config.NoTests); err != nil { + return fmt.Errorf("singleton template output: %w", err) + } + + dirExtMap := groupTemplates(o.tableTemplates) + + for _, table := range data.Tables { + data.Table = table + + // Generate the regular templates + if err := generateOutput(o, dirExtMap, o.tableTemplates, data, goVersion, s.Config.NoTests); err != nil { + return fmt.Errorf("unable to generate output: %w", err) + } + } + + if len(data.QueryFolder.Files) == 0 { + continue + } + + dirExtMap = groupTemplates(o.queryTemplates) + for _, file := range data.QueryFolder.Files { + data.QueryFile = file - for _, table := range data.Tables { - data.Table = table + // We do this so that the name of the file is correct + base := filepath.Base(file.Path) + data.Table = drivers.Table[C, I]{ + Name: base[:len(base)-4], + } - // Generate the regular templates - if err := generateOutput(o, dirExtMap, data, goVersion, s.Config.NoTests); err != nil { - return fmt.Errorf("unable to generate output: %w", err) + if err := generateOutput(o, dirExtMap, o.queryTemplates, data, goVersion, s.Config.NoTests); err != nil { + return fmt.Errorf("unable to generate output: %w", err) + } } } } diff --git a/gen/output.go b/gen/output.go index 299e1fe..6b6aee6 100644 --- a/gen/output.go +++ b/gen/output.go @@ -50,6 +50,14 @@ var ( type Output struct { // The key has to be unique in a gen.State // it also makes it possible to target modifing a specific output + // There are special keys that are reserved for internal use + // * "models" - for model templates. + // This is also used to set `ModelsPackage` in the template data + // * "factory" - for factory templates + // * "queries" - for query templates. + // - This is run once for each query folder + // - The PkgName is set to the folder name in each run + // - The OutFolder is set to the same folder Key string PkgName string @@ -58,6 +66,7 @@ type Output struct { singletonTemplates *template.Template tableTemplates *template.Template + queryTemplates *template.Template // Scratch buffers used as staging area for preparing parsed template data templateByteBuffer *bytes.Buffer @@ -65,12 +74,15 @@ type Output struct { } func (o *Output) numTemplates() int { - return len(o.singletonTemplates.Templates()) + len(o.tableTemplates.Templates()) + return 0 + + len(o.singletonTemplates.Templates()) + + len(o.tableTemplates.Templates()) + + len(o.queryTemplates.Templates()) } // initOutFolders creates the folders that will hold the generated output. func (o *Output) initOutFolders(wipe bool) error { - if wipe { + if wipe && !strings.Contains(o.OutFolder, "quer") { if err := os.RemoveAll(o.OutFolder); err != nil { return fmt.Errorf("unable to wipe output folder: %w", err) } @@ -99,7 +111,7 @@ func (o *Output) initOutFolders(wipe bool) error { } if err := os.MkdirAll(o.OutFolder, os.ModePerm); err != nil { - return err + return fmt.Errorf("unable to create output folder %q: %w", o.OutFolder, err) } return nil @@ -124,6 +136,7 @@ func (o *Output) initTemplates(funcs template.FuncMap) error { o.singletonTemplates = template.New("") o.tableTemplates = template.New("") + o.queryTemplates = template.New("") if err := addTemplates(o.singletonTemplates, o.Templates, funcs, ".", true); err != nil { return fmt.Errorf("failed to add singleton templates: %w", err) @@ -133,6 +146,10 @@ func (o *Output) initTemplates(funcs template.FuncMap) error { return fmt.Errorf("failed to add table templates: %w", err) } + if err := addTemplates(o.queryTemplates, o.Templates, funcs, "query", false); err != nil { + return fmt.Errorf("failed to add query templates: %w", err) + } + return nil } @@ -157,6 +174,9 @@ func addTemplates(tpl *template.Template, tempFSs []fs.FS, funcs template.FuncMa entries, err := fs.ReadDir(tempFS, ".") if err != nil { + if errors.Is(err, fs.ErrNotExist) { + continue + } return fmt.Errorf("failed to read dir %q: %w", dir, err) } @@ -215,11 +235,11 @@ type executeTemplateData[T, C, I any] struct { } // generateOutput builds the file output and sends it to outHandler for saving -func generateOutput[T, C, I any](o *Output, dirExts dirExtMap, data *TemplateData[T, C, I], goVersion string, noTests bool) error { +func generateOutput[T, C, I any](o *Output, dirExts dirExtMap, tpl *template.Template, data *TemplateData[T, C, I], goVersion string, noTests bool) error { if err := executeTemplates(executeTemplateData[T, C, I]{ output: o, data: data, - templates: o.tableTemplates, + templates: tpl, dirExtensions: dirExts, }, goVersion, false); err != nil { return fmt.Errorf("execute templates: %w", err) @@ -232,7 +252,7 @@ func generateOutput[T, C, I any](o *Output, dirExts dirExtMap, data *TemplateDat if err := executeTemplates(executeTemplateData[T, C, I]{ output: o, data: data, - templates: o.tableTemplates, + templates: tpl, dirExtensions: dirExts, }, goVersion, true); err != nil { return fmt.Errorf("execute test templates: %w", err) diff --git a/gen/templates.go b/gen/templates.go index f2d1eb4..748b790 100644 --- a/gen/templates.go +++ b/gen/templates.go @@ -30,6 +30,7 @@ var sqliteTemplates embed.FS var ( ModelTemplates, _ = fs.Sub(templates, "templates/models") FactoryTemplates, _ = fs.Sub(templates, "templates/factory") + QueriesTemplates, _ = fs.Sub(templates, "templates/queries") MySQLModelTemplates, _ = fs.Sub(mysqlTemplates, "bobgen-mysql/templates/models") PSQLModelTemplates, _ = fs.Sub(psqlTemplates, "bobgen-psql/templates/models") SQLiteModelTemplates, _ = fs.Sub(sqliteTemplates, "bobgen-sqlite/templates/models") @@ -82,6 +83,9 @@ type TemplateData[T, C, I any] struct { Table drivers.Table[C, I] Tables drivers.Tables[C, I] + QueryFile drivers.QueryFile + QueryFolder drivers.QueryFolder + QueryFolders []drivers.QueryFolder Enums []drivers.Enum Aliases drivers.Aliases Types drivers.Types