Skip to content

Commit 12335e8

Browse files
committed
Add options to split files (based on sqlc-dev#3874)
1 parent a60f370 commit 12335e8

File tree

12 files changed

+183
-27
lines changed

12 files changed

+183
-27
lines changed

docs/howto/separate-models-file.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Separating models file
2+
3+
By default, sqlc uses a single package to place all the generated code. But you may want to separate
4+
the generated models file into another package for loose coupling purposes in your project.
5+
6+
To do this, you can use the following configuration:
7+
8+
```yaml
9+
version: "2"
10+
sql:
11+
- engine: "postgresql"
12+
queries: "queries.sql"
13+
schema: "schema.sql"
14+
gen:
15+
go:
16+
out: "internal/" # Base directory for the generated files. You can also just use "."
17+
sql_package: "pgx/v5"
18+
package: "sqlcrepo"
19+
output_batch_file_name: "db/sqlcrepo/batch.go"
20+
output_db_file_name: "db/sqlcrepo/db.go"
21+
output_querier_file_name: "db/sqlcrepo/querier.go"
22+
output_copyfrom_file_name: "db/sqlcrepo/copyfrom.go"
23+
output_query_files_directory: "db/sqlcrepo/"
24+
output_models_file_name: "business/entities/models.go"
25+
output_models_package: "entities"
26+
models_package_import_path: "example.com/project/module-path/internal/business/entities"
27+
```
28+
29+
This configuration will generate files in the `internal/db/sqlcrepo` directory with `sqlcrepo`
30+
package name, except for the models file which will be generated in the `internal/business/entities`
31+
directory. The generated models file will use the package name `entities` and it will be imported in
32+
the other generated files using the given
33+
`"example.com/project/module-path/internal/business/entities"` import path when needed.
34+
35+
The generated files will look like this:
36+
37+
```
38+
my-app/
39+
├── internal/
40+
│ ├── db/
41+
│ │ └── sqlcrepo/
42+
│ │ ├── db.go
43+
│ │ └── queries.sql.go
44+
│ └── business/
45+
│ └── entities/
46+
│ └── models.go
47+
├── queries.sql
48+
├── schema.sql
49+
└── sqlc.yaml
50+
```

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ code ever again.
6666
howto/embedding.md
6767
howto/overrides.md
6868
howto/rename.md
69+
howto/separate-models-file.md
6970

7071
.. toctree::
7172
:maxdepth: 3

docs/reference/config.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,16 @@ The `gen` mapping supports the following keys:
183183
- Customize the name of the db file. Defaults to `db.go`.
184184
- `output_models_file_name`:
185185
- Customize the name of the models file. Defaults to `models.go`.
186+
- `output_models_package`:
187+
- Package name of the models file. Used when models file is in a different package. Defaults to value of `package` option.
188+
- `models_package_import_path`:
189+
- Import path of the models package when models file is in a different package. Optional.
186190
- `output_querier_file_name`:
187191
- Customize the name of the querier file. Defaults to `querier.go`.
188192
- `output_copyfrom_file_name`:
189193
- Customize the name of the copyfrom file. Defaults to `copyfrom.go`.
194+
- `output_query_files_directory`:
195+
- Directory where the generated query files will be placed. Defaults to the value of `out` option.
190196
- `output_files_suffix`:
191197
- If specified the suffix will be added to the name of the generated files.
192198
- `query_parameter_limit`:

internal/codegen/golang/gen.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"go/format"
10+
"path/filepath"
1011
"strings"
1112
"text/template"
1213

@@ -126,7 +127,7 @@ func Generate(ctx context.Context, req *plugin.GenerateRequest) (*plugin.Generat
126127
}
127128

128129
if options.OmitUnusedStructs {
129-
enums, structs = filterUnusedStructs(enums, structs, queries)
130+
enums, structs = filterUnusedStructs(options, enums, structs, queries)
130131
}
131132

132133
if err := validate(options, enums, structs, queries); err != nil {
@@ -216,6 +217,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
216217
"imports": i.Imports,
217218
"hasImports": i.HasImports,
218219
"hasPrefix": strings.HasPrefix,
220+
"trimPrefix": strings.TrimPrefix,
219221

220222
// These methods are Go specific, they do not belong in the codegen package
221223
// (as that is language independent)
@@ -237,14 +239,15 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
237239

238240
output := map[string]string{}
239241

240-
execute := func(name, templateName string) error {
242+
execute := func(name, packageName, templateName string) error {
241243
imports := i.Imports(name)
242244
replacedQueries := replaceConflictedArg(imports, queries)
243245

244246
var b bytes.Buffer
245247
w := bufio.NewWriter(&b)
246248
tctx.SourceName = name
247249
tctx.GoQueries = replacedQueries
250+
tctx.Package = packageName
248251
err := tmpl.ExecuteTemplate(w, templateName, &tctx)
249252
w.Flush()
250253
if err != nil {
@@ -256,8 +259,13 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
256259
return fmt.Errorf("source error: %w", err)
257260
}
258261

259-
if templateName == "queryFile" && options.OutputFilesSuffix != "" {
260-
name += options.OutputFilesSuffix
262+
if templateName == "queryFile" {
263+
if options.OutputQueryFilesDirectory != "" {
264+
name = filepath.Join(options.OutputQueryFilesDirectory, name)
265+
}
266+
if options.OutputFilesSuffix != "" {
267+
name += options.OutputFilesSuffix
268+
}
261269
}
262270

263271
if !strings.HasSuffix(name, ".go") {
@@ -289,24 +297,29 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
289297
batchFileName = options.OutputBatchFileName
290298
}
291299

292-
if err := execute(dbFileName, "dbFile"); err != nil {
300+
modelsPackageName := options.Package
301+
if options.OutputModelsPackage != "" {
302+
modelsPackageName = options.OutputModelsPackage
303+
}
304+
305+
if err := execute(dbFileName, options.Package, "dbFile"); err != nil {
293306
return nil, err
294307
}
295-
if err := execute(modelsFileName, "modelsFile"); err != nil {
308+
if err := execute(modelsFileName, modelsPackageName, "modelsFile"); err != nil {
296309
return nil, err
297310
}
298311
if options.EmitInterface {
299-
if err := execute(querierFileName, "interfaceFile"); err != nil {
312+
if err := execute(querierFileName, options.Package, "interfaceFile"); err != nil {
300313
return nil, err
301314
}
302315
}
303316
if tctx.UsesCopyFrom {
304-
if err := execute(copyfromFileName, "copyfromFile"); err != nil {
317+
if err := execute(copyfromFileName, options.Package, "copyfromFile"); err != nil {
305318
return nil, err
306319
}
307320
}
308321
if tctx.UsesBatch {
309-
if err := execute(batchFileName, "batchFile"); err != nil {
322+
if err := execute(batchFileName, options.Package, "batchFile"); err != nil {
310323
return nil, err
311324
}
312325
}
@@ -317,7 +330,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
317330
}
318331

319332
for source := range files {
320-
if err := execute(source, "queryFile"); err != nil {
333+
if err := execute(source, options.Package, "queryFile"); err != nil {
321334
return nil, err
322335
}
323336
}
@@ -367,7 +380,7 @@ func checkNoTimesForMySQLCopyFrom(queries []Query) error {
367380
return nil
368381
}
369382

370-
func filterUnusedStructs(enums []Enum, structs []Struct, queries []Query) ([]Enum, []Struct) {
383+
func filterUnusedStructs(options *opts.Options, enums []Enum, structs []Struct, queries []Query) ([]Enum, []Struct) {
371384
keepTypes := make(map[string]struct{})
372385

373386
for _, query := range queries {
@@ -394,16 +407,23 @@ func filterUnusedStructs(enums []Enum, structs []Struct, queries []Query) ([]Enu
394407

395408
keepEnums := make([]Enum, 0, len(enums))
396409
for _, enum := range enums {
397-
_, keep := keepTypes[enum.Name]
398-
_, keepNull := keepTypes["Null"+enum.Name]
410+
var enumType string
411+
if options.ModelsPackageImportPath != "" {
412+
enumType = options.OutputModelsPackage + "." + enum.Name
413+
} else {
414+
enumType = enum.Name
415+
}
416+
417+
_, keep := keepTypes[enumType]
418+
_, keepNull := keepTypes["Null"+enumType]
399419
if keep || keepNull {
400420
keepEnums = append(keepEnums, enum)
401421
}
402422
}
403423

404424
keepStructs := make([]Struct, 0, len(structs))
405425
for _, st := range structs {
406-
if _, ok := keepTypes[st.Name]; ok {
426+
if _, ok := keepTypes[st.Type()]; ok {
407427
keepStructs = append(keepStructs, st)
408428
}
409429
}

internal/codegen/golang/imports.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ var pqtypeTypes = map[string]struct{}{
160160
"pqtype.NullRawMessage": {},
161161
}
162162

163-
func buildImports(options *opts.Options, queries []Query, uses func(string) bool) (map[string]struct{}, map[ImportSpec]struct{}) {
163+
func buildImports(options *opts.Options, queries []Query, outputFile OutputFile, uses func(string) bool) (map[string]struct{}, map[ImportSpec]struct{}) {
164164
pkg := make(map[ImportSpec]struct{})
165165
std := make(map[string]struct{})
166166

@@ -243,11 +243,52 @@ func buildImports(options *opts.Options, queries []Query, uses func(string) bool
243243
}
244244
}
245245

246+
requiresModelsPackageImport := func() bool {
247+
if options.ModelsPackageImportPath == "" {
248+
return false
249+
}
250+
251+
for _, q := range queries {
252+
// Check if the return type is from models package (possibly a model struct or an enum)
253+
if q.hasRetType() && strings.HasPrefix(q.Ret.Type(), options.OutputModelsPackage+".") {
254+
return true
255+
}
256+
257+
// Check if the return type struct contains a type from models package (possibly an enum field or an embedded struct)
258+
if outputFile != OutputFileInterface && q.hasRetType() && q.Ret.IsStruct() {
259+
for _, f := range q.Ret.Struct.Fields {
260+
if strings.HasPrefix(f.Type, options.OutputModelsPackage+".") {
261+
return true
262+
}
263+
}
264+
}
265+
266+
// Check if the argument type is from models package (possibly an enum)
267+
if !q.Arg.isEmpty() && strings.HasPrefix(q.Arg.Type(), options.OutputModelsPackage+".") {
268+
return true
269+
}
270+
271+
// Check if the argument struct contains a type from models package (possibly an enum field)
272+
if outputFile != OutputFileInterface && !q.Arg.isEmpty() && q.Arg.IsStruct() {
273+
for _, f := range q.Arg.Struct.Fields {
274+
if strings.HasPrefix(f.Type, options.OutputModelsPackage+".") {
275+
return true
276+
}
277+
}
278+
}
279+
280+
}
281+
return false
282+
}
283+
if requiresModelsPackageImport() {
284+
pkg[ImportSpec{Path: options.ModelsPackageImportPath}] = struct{}{}
285+
}
286+
246287
return std, pkg
247288
}
248289

249290
func (i *importer) interfaceImports() fileImports {
250-
std, pkg := buildImports(i.Options, i.Queries, func(name string) bool {
291+
std, pkg := buildImports(i.Options, i.Queries, OutputFileInterface, func(name string) bool {
251292
for _, q := range i.Queries {
252293
if q.hasRetType() {
253294
if usesBatch([]Query{q}) {
@@ -272,7 +313,7 @@ func (i *importer) interfaceImports() fileImports {
272313
}
273314

274315
func (i *importer) modelImports() fileImports {
275-
std, pkg := buildImports(i.Options, nil, i.usesType)
316+
std, pkg := buildImports(i.Options, nil, OutputFileModel, i.usesType)
276317

277318
if len(i.Enums) > 0 {
278319
std["fmt"] = struct{}{}
@@ -311,7 +352,7 @@ func (i *importer) queryImports(filename string) fileImports {
311352
}
312353
}
313354

314-
std, pkg := buildImports(i.Options, gq, func(name string) bool {
355+
std, pkg := buildImports(i.Options, gq, OutputFileQuery, func(name string) bool {
315356
for _, q := range gq {
316357
if q.hasRetType() {
317358
if q.Ret.EmitStruct() {
@@ -416,7 +457,7 @@ func (i *importer) copyfromImports() fileImports {
416457
copyFromQueries = append(copyFromQueries, q)
417458
}
418459
}
419-
std, pkg := buildImports(i.Options, copyFromQueries, func(name string) bool {
460+
std, pkg := buildImports(i.Options, copyFromQueries, OutputFileCopyfrom, func(name string) bool {
420461
for _, q := range copyFromQueries {
421462
if q.hasRetType() {
422463
if strings.HasPrefix(q.Ret.Type(), name) {
@@ -451,7 +492,7 @@ func (i *importer) batchImports() fileImports {
451492
batchQueries = append(batchQueries, q)
452493
}
453494
}
454-
std, pkg := buildImports(i.Options, batchQueries, func(name string) bool {
495+
std, pkg := buildImports(i.Options, batchQueries, OutputFileBatch, func(name string) bool {
455496
for _, q := range batchQueries {
456497
if q.hasRetType() {
457498
if q.Ret.EmitStruct() {

internal/codegen/golang/opts/options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ type Options struct {
3535
OutputBatchFileName string `json:"output_batch_file_name,omitempty" yaml:"output_batch_file_name"`
3636
OutputDbFileName string `json:"output_db_file_name,omitempty" yaml:"output_db_file_name"`
3737
OutputModelsFileName string `json:"output_models_file_name,omitempty" yaml:"output_models_file_name"`
38+
OutputModelsPackage string `json:"output_models_package,omitempty" yaml:"output_models_package"`
39+
ModelsPackageImportPath string `json:"models_package_import_path,omitempty" yaml:"models_package_import_path"`
3840
OutputQuerierFileName string `json:"output_querier_file_name,omitempty" yaml:"output_querier_file_name"`
3941
OutputCopyfromFileName string `json:"output_copyfrom_file_name,omitempty" yaml:"output_copyfrom_file_name"`
42+
OutputQueryFilesDirectory string `json:"output_query_files_directory,omitempty" yaml:"output_query_files_directory"`
4043
OutputFilesSuffix string `json:"output_files_suffix,omitempty" yaml:"output_files_suffix"`
4144
InflectionExcludeTableNames []string `json:"inflection_exclude_table_names,omitempty" yaml:"inflection_exclude_table_names"`
4245
WrapErrors bool `json:"wrap_errors,omitempty" yaml:"wrap_errors"`
@@ -152,5 +155,12 @@ func ValidateOpts(opts *Options) error {
152155
return fmt.Errorf("invalid options: query parameter limit must not be negative")
153156
}
154157

158+
if opts.OutputModelsPackage != "" && opts.ModelsPackageImportPath == "" {
159+
return fmt.Errorf("invalid options: models_package_import_path must be set when output_models_package is used")
160+
}
161+
if opts.ModelsPackageImportPath != "" && opts.OutputModelsPackage == "" {
162+
return fmt.Errorf("invalid options: output_models_package must be set when models_package_import_path is used")
163+
}
164+
155165
return nil
156166
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package golang
2+
3+
type OutputFile string
4+
5+
const (
6+
OutputFileModel OutputFile = "modelFile"
7+
OutputFileQuery OutputFile = "queryFile"
8+
OutputFileDb OutputFile = "dbFile"
9+
OutputFileInterface OutputFile = "interfaceFile"
10+
OutputFileCopyfrom OutputFile = "copyfromFile"
11+
OutputFileBatch OutputFile = "batchFile"
12+
)

internal/codegen/golang/postgresql_type.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -571,17 +571,24 @@ func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugi
571571

572572
for _, enum := range schema.Enums {
573573
if rel.Name == enum.Name && rel.Schema == schema.Name {
574+
enumName := ""
574575
if notNull {
575576
if schema.Name == req.Catalog.DefaultSchema {
576-
return StructName(enum.Name, options)
577+
enumName = StructName(enum.Name, options)
578+
} else {
579+
enumName = StructName(schema.Name+"_"+enum.Name, options)
577580
}
578-
return StructName(schema.Name+"_"+enum.Name, options)
579581
} else {
580582
if schema.Name == req.Catalog.DefaultSchema {
581-
return "Null" + StructName(enum.Name, options)
583+
enumName = "Null" + StructName(enum.Name, options)
584+
} else {
585+
enumName = "Null" + StructName(schema.Name+"_"+enum.Name, options)
582586
}
583-
return "Null" + StructName(schema.Name+"_"+enum.Name, options)
584587
}
588+
if options.ModelsPackageImportPath != "" {
589+
return options.OutputModelsPackage + "." + enumName
590+
}
591+
return enumName
585592
}
586593
}
587594

internal/codegen/golang/query.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (v QueryValue) Type() string {
8888
return v.Typ
8989
}
9090
if v.Struct != nil {
91-
return v.Struct.Name
91+
return v.Struct.Type()
9292
}
9393
panic("no type for QueryValue: " + v.Name)
9494
}

0 commit comments

Comments
 (0)