Skip to content

Commit 83b5acc

Browse files
committed
generate-database: Reduce to single go:generate per package
Signed-off-by: Lucas Bremgartner <[email protected]>
1 parent 0d00514 commit 83b5acc

19 files changed

+389
-307
lines changed

cmd/generate-database/README.md

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,55 @@ predictability.
1414

1515
### Initialization
1616

17-
Generally the first thing we will want to do for any newly generated file is to
18-
establish the command, the target file, and ensure the file has been cleared of
19-
content:
17+
#### Package global
2018

21-
```go
22-
//go:generate -command mapper generate-database db mapper -t instances.mapper.go
23-
//go:generate mapper generate -i -b "//go:build linux && cgo && !agent"
19+
Once per package, that uses `generate-database` for generation of database
20+
statements and associated `go` functions, `generate-database` needs to be invoked
21+
using the following `go:generate` instruction:
2422

23+
```go
24+
//go:generate generate-database db mapper generate
2525
```
2626

27-
This will initiate a call to `generate-database db mapper -t instances.mapper.go mapper generate`,
27+
This will initiate a call to `generate-database db mapper generate`,
2828
which will then search for `//generate-database:mapper` directives in the same file
2929
and process those.
3030

31+
#### File
32+
33+
Generally the first thing we will want to do for any newly generated file is to
34+
ensure the file has been cleared of content:
35+
36+
```go
37+
//generate-database:mapper target instances.mapper.go
38+
//generate-database:mapper reset -i -b "//go:build linux && cgo && !agent"
39+
```
40+
3141
### Generation Directive Arguments
3242

3343
The generation directive aruments have the following form:
3444

35-
`//generate-database:mapper <command> <entity> <kind> <config...>`
45+
`//generate-database:mapper <command> flags <kind> <args...>`
46+
47+
The following flags are available:
48+
49+
* `--build` / `-b`: build comment to include (commands: `reset`)
50+
* `--interface` / `-i`: create interface files (commands: `reset`, `method`)
51+
* `--entity` / `-e`: database entity to generate the method or statement for (commands: `stmt`, `method`)
3652

3753
Example:
3854

39-
* `//generate-database:mapper stmt instance objects table=table_name`
55+
* `//generate-database:mapper stmt -e instance objects table=table_name`
4056

4157
The `table` key can be used to override the generated table name for a specified one.
4258

43-
* `//generate-database:mapper stmt method instance Create references=Config,Device`
59+
* `//generate-database:mapper method -i -e instance Create references=Config,Device`
4460

4561
For some tables (defined below under [Additional Information](#Additional-Information) as [EntityTable](#EntityTable), the `references=<ReferenceEntity>` key can be provided with the name of
4662
a [ReferenceTable](#ReferenceTable) or [MapTable](#MapTable) struct. This directive would produce `CreateInstance` in addition to `CreateInstanceConfig` and `CreateInstanceDevices`:
4763

48-
* `//generate-database:mapper method instance_profile Create struct=Instance`
49-
* `//generate-database:mapper method instance_profile Create struct=Profile`
64+
* `//generate-database:mapper method -i -e instance_profile Create struct=Instance`
65+
* `//generate-database:mapper method -i -e instance_profile Create struct=Profile`
5066

5167
For some tables (defined below under [Additional Information](#Additional-Information) as [AssociationTable](#AssociationTable), `method` declarations must
5268
include a `struct=<Entity>` to indicate the directionality of the function. An invocation can be called for each direction.
@@ -70,11 +86,11 @@ Type | Description
7086
#### Examples
7187

7288
```go
73-
//generate-database:mapper stmt instance objects
74-
//generate-database:mapper stmt instance objects-by-Name-and-Project
75-
//generate-database:mapper stmt instance create
76-
//generate-database:mapper stmt instance update
77-
//generate-database:mapper stmt instance delete-by-Name-and-Project
89+
//generate-database:mapper stmt -e instance objects
90+
//generate-database:mapper stmt -e instance objects-by-Name-and-Project
91+
//generate-database:mapper stmt -e instance create
92+
//generate-database:mapper stmt -e instance update
93+
//generate-database:mapper stmt -e instance delete-by-Name-and-Project
7894
```
7995

8096
#### Statement Related Go Tags
@@ -112,14 +128,14 @@ Type | Description
112128
`DeleteMany` | Delete one or more rows from the table.
113129

114130
```go
115-
//generate-database:mapper method instance GetMany
116-
//generate-database:mapper method instance GetOne
117-
//generate-database:mapper method instance ID
118-
//generate-database:mapper method instance Exist
119-
//generate-database:mapper method instance Create
120-
//generate-database:mapper method instance Update
121-
//generate-database:mapper method instance DeleteOne-by-Project-and-Name
122-
//generate-database:mapper method instance DeleteMany-by-Name
131+
//generate-database:mapper method -i -e instance GetMany
132+
//generate-database:mapper method -i -e instance GetOne
133+
//generate-database:mapper method -i -e instance ID
134+
//generate-database:mapper method -i -e instance Exist
135+
//generate-database:mapper method -i -e instance Create
136+
//generate-database:mapper method -i -e instance Update
137+
//generate-database:mapper method -i -e instance DeleteOne-by-Project-and-Name
138+
//generate-database:mapper method -i -e instance DeleteMany-by-Name
123139
```
124140

125141
### Additional Information
@@ -168,8 +184,8 @@ Real world invocation of these statements and functions should be done through a
168184
Example:
169185

170186
```go
171-
//generate-database:mapper stmt device create
172-
//generate-database:mapper method device Create
187+
//generate-database:mapper stmt -e device create
188+
//generate-database:mapper method -e device Create
173189

174190
type Device struct {
175191
ID int
@@ -179,7 +195,7 @@ type Device struct {
179195
}
180196

181197
//...
182-
//generate-database:mapper method instance Create references=Device
198+
//generate-database:mapper method -e instance Create references=Device
183199
// This will produce a function called `CreateInstanceDevices`.
184200
```
185201

@@ -191,8 +207,8 @@ On the SQL side, this is treated exactly like a `ReferenceTable`, but on the `go
191207
Example:
192208

193209
```go
194-
//generate-database:mapper stmt config create
195-
//generate-database:mapper method config Create
210+
//generate-database:mapper stmt -e config create
211+
//generate-database:mapper method -e config Create
196212

197213
type Config struct {
198214
ID int
@@ -202,7 +218,7 @@ type Config struct {
202218
}
203219

204220
//...
205-
//generate-database:mapper method instance Create references=Config
221+
//generate-database:mapper method -e instance Create references=Config
206222
// This will produce a function called `CreateInstanceConfig`, which will return a `map[string]string`.
207223
```
208224

@@ -217,8 +233,8 @@ An invocation can be called for each direction.
217233
Example:
218234

219235
```go
220-
//generate-database:mapper method instance_profile Create struct=Instance
221-
//generate-database:mapper method instance_profile Create struct=Profile
236+
//generate-database:mapper method -i -e instance_profile Create struct=Instance
237+
//generate-database:mapper method -i -e instance_profile Create struct=Profile
222238

223239
type InstanceProfile struct {
224240
InstanceID int

cmd/generate-database/db.go

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
package main
44

55
import (
6+
"encoding/csv"
67
"errors"
78
"fmt"
89
"go/build"
910
"os"
10-
"path/filepath"
1111
"strings"
1212

1313
"github.com/spf13/cobra"
14+
"github.com/spf13/pflag"
1415
"golang.org/x/tools/go/packages"
1516

1617
"github.com/lxc/incus/v6/cmd/generate-database/db"
@@ -64,9 +65,6 @@ func newDbMapper() *cobra.Command {
6465
}
6566

6667
func newDbMapperGenerate() *cobra.Command {
67-
var target string
68-
var build string
69-
var iface bool
7068
var pkg string
7169

7270
cmd := &cobra.Command{
@@ -77,73 +75,74 @@ func newDbMapperGenerate() *cobra.Command {
7775
return errors.New("GOPACKAGE environment variable is not set")
7876
}
7977

80-
if os.Getenv("GOFILE") == "" {
81-
return errors.New("GOFILE environment variable is not set")
82-
}
83-
84-
return generate(target, build, iface, pkg)
78+
return generate(pkg)
8579
},
8680
}
8781

8882
flags := cmd.Flags()
89-
flags.BoolVarP(&iface, "interface", "i", false, "create interface files")
90-
flags.StringVarP(&target, "target", "t", "-", "target source file to generate")
91-
flags.StringVarP(&build, "build", "b", "", "build comment to include")
9283
flags.StringVarP(&pkg, "package", "p", "", "Go package where the entity struct is declared")
9384

9485
return cmd
9586
}
9687

9788
const prefix = "//generate-database:mapper "
9889

99-
func generate(target string, build string, iface bool, pkg string) error {
100-
err := file.Reset(target, db.Imports, build, iface)
101-
if err != nil {
102-
return err
103-
}
104-
90+
func generate(pkg string) error {
10591
parsedPkg, err := packageLoad(pkg)
10692
if err != nil {
10793
return err
10894
}
10995

11096
registeredSQLStmts := map[string]string{}
11197
for _, goFile := range parsedPkg.CompiledGoFiles {
112-
if filepath.Base(goFile) != os.Getenv("GOFILE") {
113-
continue
114-
}
115-
11698
body, err := os.ReadFile(goFile)
11799
if err != nil {
118100
return err
119101
}
120102

103+
// Reset target to stdout
104+
target := "-"
105+
121106
lines := strings.Split(string(body), "\n")
122107
for _, line := range lines {
123108
// Lazy matching for prefix, does not consider Go syntax and therefore
124109
// lines starting with prefix, that are part of e.g. multiline strings
125110
// match as well. This is highly unlikely to cause false positives.
126111
if strings.HasPrefix(line, prefix) {
127112
line = strings.TrimPrefix(line, prefix)
128-
args := strings.Split(line, " ")
129113

130-
command := args[0]
131-
entity := args[1]
132-
kind := args[2]
133-
config, err := parseParams(args[3:])
114+
// Use csv parser to properly handle arguments surrounded by double quotes.
115+
r := csv.NewReader(strings.NewReader(line))
116+
r.Comma = ' ' // space
117+
args, err := r.Read()
134118
if err != nil {
135119
return err
136120
}
137121

122+
if len(args) == 0 {
123+
return fmt.Errorf("command missing")
124+
}
125+
126+
command := args[0]
127+
138128
switch command {
129+
case "target":
130+
if len(args) != 2 {
131+
return fmt.Errorf("invalid arguments for command target, one argument for the target filename: %s", line)
132+
}
133+
134+
target = args[1]
135+
case "reset":
136+
err = commandReset(args[1:], target)
137+
139138
case "stmt":
140-
err = generateStmt(target, parsedPkg, entity, kind, config, registeredSQLStmts)
139+
err = commandStmt(args[1:], target, parsedPkg, registeredSQLStmts)
141140

142141
case "method":
143-
err = generateMethod(target, iface, parsedPkg, entity, kind, config, registeredSQLStmts)
142+
err = commandMethod(args[1:], target, parsedPkg, registeredSQLStmts)
144143

145144
default:
146-
err = fmt.Errorf("undefined command: %s", command)
145+
err = fmt.Errorf("unknown command: %s", command)
147146
}
148147

149148
if err != nil {
@@ -156,22 +155,83 @@ func generate(target string, build string, iface bool, pkg string) error {
156155
return nil
157156
}
158157

159-
func generateStmt(target string, parsedPkg *packages.Package, entity, kind string, config map[string]string, registeredSQLStmts map[string]string) error {
160-
stmt, err := db.NewStmt(parsedPkg, entity, kind, config, registeredSQLStmts)
158+
func commandReset(commandLine []string, target string) error {
159+
var err error
160+
161+
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
162+
iface := flags.BoolP("interface", "i", false, "create interface files")
163+
buildComment := flags.StringP("build", "b", "", "build comment to include")
164+
165+
err = flags.Parse(commandLine)
161166
if err != nil {
162167
return err
163168
}
164169

165-
return file.Append(entity, target, stmt, false)
170+
err = file.Reset(target, db.Imports, *buildComment, *iface)
171+
if err != nil {
172+
return err
173+
}
174+
175+
return nil
166176
}
167177

168-
func generateMethod(target string, iface bool, parsedPkg *packages.Package, entity, kind string, config map[string]string, registeredSQLStmts map[string]string) error {
169-
method, err := db.NewMethod(parsedPkg, entity, kind, config, registeredSQLStmts)
178+
func commandStmt(commandLine []string, target string, parsedPkg *packages.Package, registeredSQLStmts map[string]string) error {
179+
var err error
180+
181+
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
182+
entity := flags.StringP("entity", "e", "", "database entity to generate the statement for")
183+
184+
err = flags.Parse(commandLine)
185+
if err != nil {
186+
return err
187+
}
188+
189+
if len(flags.Args()) < 1 {
190+
return fmt.Errorf("argument <kind> missing for stmt command")
191+
}
192+
193+
kind := flags.Arg(0)
194+
config, err := parseParams(flags.Args()[1:])
195+
if err != nil {
196+
return err
197+
}
198+
199+
stmt, err := db.NewStmt(parsedPkg, *entity, kind, config, registeredSQLStmts)
200+
if err != nil {
201+
return err
202+
}
203+
204+
return file.Append(*entity, target, stmt, false)
205+
}
206+
207+
func commandMethod(commandLine []string, target string, parsedPkg *packages.Package, registeredSQLStmts map[string]string) error {
208+
var err error
209+
210+
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
211+
iface := flags.BoolP("interface", "i", false, "create interface files")
212+
entity := flags.StringP("entity", "e", "", "database entity to generate the method for")
213+
214+
err = flags.Parse(commandLine)
215+
if err != nil {
216+
return err
217+
}
218+
219+
if len(flags.Args()) < 1 {
220+
return fmt.Errorf("argument <kind> missing for method command")
221+
}
222+
223+
kind := flags.Arg(0)
224+
config, err := parseParams(flags.Args()[1:])
225+
if err != nil {
226+
return err
227+
}
228+
229+
method, err := db.NewMethod(parsedPkg, *entity, kind, config, registeredSQLStmts)
170230
if err != nil {
171231
return err
172232
}
173233

174-
return file.Append(entity, target, method, iface)
234+
return file.Append(*entity, target, method, *iface)
175235
}
176236

177237
func packageLoad(pkg string) (*packages.Package, error) {

0 commit comments

Comments
 (0)