Skip to content

Commit 1d2cff0

Browse files
committed
Add insert column exclusion option
1 parent f2152c1 commit 1d2cff0

8 files changed

Lines changed: 160 additions & 22 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ sql:
7171
- "CopyUsers"
7272
exclude:
7373
- "DeleteUser"
74+
insert_columns:
75+
exclude:
76+
- "id"
77+
- "created_at"
78+
- "updated_at"
7479
```
7580
7681
Use `options.tables` to control which tables get query files. Entries may be
@@ -95,6 +100,12 @@ are query names (e.g. `GetUser`, `ListPostsByTitle`).
95100
> list form (`queries: ["CopyUsers"]`) is no longer supported — move those
96101
> entries under `queries.include`.
97102

103+
Use `options.insert_columns.exclude` to remove database-owned columns from
104+
generated `INSERT`, batch insert, and `COPY` queries. Entries may be plain
105+
column names (`id`), table-qualified names (`users.id`), or schema-qualified
106+
names (`auth.users.id`). This is useful for columns with database defaults such
107+
as generated IDs and timestamps.
108+
98109
### Default queries (always generated)
99110

100111
Primary key CRUD operations, List queries (including FK-index-based list

internal/sqlc/config.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ type Codegen struct {
7171

7272
// CodegenOptions holds plugin-specific options for the gen-queries plugin.
7373
type CodegenOptions struct {
74-
Queries QueryOptions `yaml:"queries,omitempty"`
75-
Tables TableOptions `yaml:"tables,omitempty"`
74+
Queries QueryOptions `yaml:"queries,omitempty"`
75+
Tables TableOptions `yaml:"tables,omitempty"`
76+
InsertColumns ColumnOptions `yaml:"insert_columns,omitempty"`
7677
}
7778

7879
// QueryOptions holds query-level filtering options for the gen-queries plugin.
@@ -92,6 +93,11 @@ type TableOptions struct {
9293
Exclude []string `yaml:"exclude,omitempty"`
9394
}
9495

96+
// ColumnOptions holds column-level filtering options.
97+
type ColumnOptions struct {
98+
Exclude []string `yaml:"exclude,omitempty"`
99+
}
100+
95101
// GetOptions returns the CodegenOptions for the gen-queries plugin.
96102
// If no matching codegen entry is found, returns an empty CodegenOptions.
97103
func (s *SQL) GetOptions() CodegenOptions {
@@ -148,6 +154,18 @@ func (s *SQL) GetExcludeSet() map[string]bool {
148154
return excludeSet
149155
}
150156

157+
// GetInsertColumnExcludeSet returns the deny-list of columns to skip in
158+
// generated INSERT/COPY statements. Entries may be column names, table-qualified
159+
// column names, or schema-qualified table column names.
160+
func (s *SQL) GetInsertColumnExcludeSet() map[string]bool {
161+
opts := s.GetOptions()
162+
excludeSet := make(map[string]bool, len(opts.InsertColumns.Exclude))
163+
for _, name := range opts.InsertColumns.Exclude {
164+
excludeSet[name] = true
165+
}
166+
return excludeSet
167+
}
168+
151169
// tableSelected reports whether a table should have query files generated.
152170
// Exclude always takes precedence over include; an empty include set matches
153171
// every table. Both sets are checked against the unqualified table name and
@@ -162,3 +180,16 @@ func tableSelected(includeSet, excludeSet map[string]bool, schema, table string)
162180
}
163181
return includeSet[table] || includeSet[qualified]
164182
}
183+
184+
func insertColumnSelected(excludeSet map[string]bool, schema, table, column string) bool {
185+
if excludeSet[column] {
186+
return false
187+
}
188+
if excludeSet[table+"."+column] {
189+
return false
190+
}
191+
if excludeSet[schema+"."+table+"."+column] {
192+
return false
193+
}
194+
return true
195+
}

internal/sqlc/config_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var _ = Describe("Config", func() {
2727
Expect(opts.Queries.Include).To(HaveLen(2))
2828
Expect(opts.Queries.Include).To(ContainElements("CopyUsers", "GetUserByEmail"))
2929
Expect(opts.Tables.Exclude).To(ContainElement("posts"))
30+
Expect(opts.InsertColumns.Exclude).To(ContainElements("id", "users.created_at", "public.users.updated_at"))
3031
})
3132

3233
When("the file does not exist", func() {
@@ -74,6 +75,9 @@ var _ = Describe("Config", func() {
7475
Options: sqlc.CodegenOptions{
7576
Queries: sqlc.QueryOptions{Include: []string{"ListUsers", "CopyUsers"}},
7677
Tables: sqlc.TableOptions{Exclude: []string{"audit_logs"}},
78+
InsertColumns: sqlc.ColumnOptions{
79+
Exclude: []string{"id", "created_at", "updated_at"},
80+
},
7781
},
7882
},
7983
},
@@ -82,6 +86,7 @@ var _ = Describe("Config", func() {
8286
Expect(opts.Queries.Include).To(HaveLen(2))
8387
Expect(opts.Queries.Include).To(ContainElements("ListUsers", "CopyUsers"))
8488
Expect(opts.Tables.Exclude).To(ContainElement("audit_logs"))
89+
Expect(opts.InsertColumns.Exclude).To(ContainElements("id", "created_at", "updated_at"))
8590
})
8691
})
8792

@@ -242,4 +247,35 @@ var _ = Describe("Config", func() {
242247
Expect(includeSet["posts"]).To(BeFalse())
243248
})
244249
})
250+
251+
Describe("SQL.GetInsertColumnExcludeSet", func() {
252+
It("returns an empty map when codegen is nil", func() {
253+
sql := sqlc.SQL{}
254+
excludeSet := sql.GetInsertColumnExcludeSet()
255+
Expect(excludeSet).NotTo(BeNil())
256+
Expect(excludeSet).To(BeEmpty())
257+
})
258+
259+
It("returns a map with excluded insert column names", func() {
260+
sql := sqlc.SQL{
261+
Codegen: []sqlc.Codegen{
262+
{
263+
Plugin: "gen-queries",
264+
Out: "out",
265+
Options: sqlc.CodegenOptions{
266+
InsertColumns: sqlc.ColumnOptions{
267+
Exclude: []string{"id", "todos.created_at", "public.todos.updated_at"},
268+
},
269+
},
270+
},
271+
},
272+
}
273+
excludeSet := sql.GetInsertColumnExcludeSet()
274+
Expect(excludeSet).To(HaveLen(3))
275+
Expect(excludeSet["id"]).To(BeTrue())
276+
Expect(excludeSet["todos.created_at"]).To(BeTrue())
277+
Expect(excludeSet["public.todos.updated_at"]).To(BeTrue())
278+
Expect(excludeSet["title"]).To(BeFalse())
279+
})
280+
})
245281
})

internal/sqlc/config_test_exclude.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ sql:
1818
include:
1919
- "CopyUsers"
2020
- "GetUserByEmail"
21+
insert_columns:
22+
exclude:
23+
- "id"
24+
- "users.created_at"
25+
- "public.users.updated_at"

internal/sqlc/generator.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ type Generator struct {
3030
func (x *Generator) Generate() error {
3131
// Context holds data for template execution
3232
type Context struct {
33-
Engine string
34-
Schema string
35-
Table *Table
36-
QueryInclude map[string]bool
37-
QueryExclude map[string]bool
33+
Engine string
34+
Schema string
35+
Table *Table
36+
QueryInclude map[string]bool
37+
QueryExclude map[string]bool
38+
InsertColumnExclude map[string]bool
3839
}
3940

4041
opts := map[string]any{
@@ -153,6 +154,15 @@ func (x *Generator) Generate() error {
153154
}
154155
return isDefault || ctx.QueryInclude[queryName]
155156
},
157+
"insert_columns": func(ctx Context) []Column {
158+
columns := make([]Column, 0, len(ctx.Table.Columns))
159+
for _, column := range ctx.Table.Columns {
160+
if insertColumnSelected(ctx.InsertColumnExclude, ctx.Schema, ctx.Table.Name, column.Name) {
161+
columns = append(columns, column)
162+
}
163+
}
164+
return columns
165+
},
156166
}
157167

158168
// Open the template file
@@ -168,6 +178,7 @@ func (x *Generator) Generate() error {
168178

169179
queryInclude := config.GetQueryIncludeSet()
170180
queryExclude := config.GetQueryExcludeSet()
181+
insertColumnExclude := config.GetInsertColumnExcludeSet()
171182
include := config.GetIncludeSet()
172183
exclude := config.GetExcludeSet()
173184

@@ -188,11 +199,12 @@ func (x *Generator) Generate() error {
188199
defer file.Close()
189200

190201
ctx := Context{
191-
Engine: config.Engine,
192-
Schema: schema.Name,
193-
Table: &table,
194-
QueryInclude: queryInclude,
195-
QueryExclude: queryExclude,
202+
Engine: config.Engine,
203+
Schema: schema.Name,
204+
Table: &table,
205+
QueryInclude: queryInclude,
206+
QueryExclude: queryExclude,
207+
InsertColumnExclude: insertColumnExclude,
196208
}
197209
// Execute template into buffer, then squeeze blank lines
198210
var buffer bytes.Buffer

internal/sqlc/generator_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,47 @@ var _ = Describe("Generator", func() {
231231
}
232232
})
233233

234+
It("excludes configured columns from insert queries", func() {
235+
dir := generator.Config.SQL[0].Queries
236+
generator.Config.SQL[0].Codegen = []sqlc.Codegen{
237+
{
238+
Plugin: "gen-queries",
239+
Out: dir,
240+
Options: sqlc.CodegenOptions{
241+
InsertColumns: sqlc.ColumnOptions{
242+
Exclude: []string{"id"},
243+
},
244+
},
245+
},
246+
}
247+
248+
Expect(generator.Generate()).NotTo(HaveOccurred())
249+
250+
content, err := os.ReadFile(filepath.Join(dir, "users.sql"))
251+
Expect(err).NotTo(HaveOccurred())
252+
253+
Expect(string(content)).To(ContainSubstring(`-- name: InsertUser :one
254+
INSERT INTO users (
255+
email,
256+
name
257+
) VALUES (
258+
sqlc.arg(email),
259+
sqlc.narg(name)
260+
)
261+
RETURNING *;`))
262+
Expect(string(content)).To(ContainSubstring(`-- name: ExecInsertUser :exec
263+
INSERT INTO users (
264+
email,
265+
name
266+
) VALUES (
267+
sqlc.arg(email),
268+
sqlc.narg(name)
269+
);`))
270+
Expect(string(content)).NotTo(ContainSubstring(`-- name: InsertUser :one
271+
INSERT INTO users (
272+
id,`))
273+
})
274+
234275
When("the queries directory does not exist", func() {
235276
It("returns an error", func() {
236277
for index := range generator.Config.SQL {

internal/sqlc/template/template.sql.tmpl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,11 @@ WHERE
201201
-- Returns the inserted row with all fields populated.
202202
-- name: {{$query_name}} :one
203203
INSERT INTO {{.Table.Name}} (
204-
{{ range $i, $column := .Table.Columns}}{{if $i}},
204+
{{ range $i, $column := insert_columns $}}{{if $i}},
205205
{{end}} {{$column.Name}}
206206
{{- end}}
207207
) VALUES (
208-
{{ range $i, $column := .Table.Columns}}{{if $i}},
208+
{{ range $i, $column := insert_columns $}}{{if $i}},
209209
{{end}} {{query_argument $column}}
210210
{{- end}}
211211
)
@@ -219,11 +219,11 @@ RETURNING *;
219219
-- Returns number of affected rows (should always be 1 on success).
220220
-- name: {{$query_name}} :exec
221221
INSERT INTO {{.Table.Name}} (
222-
{{ range $i, $column := .Table.Columns}}{{if $i}},
222+
{{ range $i, $column := insert_columns $}}{{if $i}},
223223
{{end}} {{$column.Name}}
224224
{{- end}}
225225
) VALUES (
226-
{{ range $i, $column := .Table.Columns}}{{if $i}},
226+
{{ range $i, $column := insert_columns $}}{{if $i}},
227227
{{end}} {{query_argument $column}}
228228
{{- end}}
229229
);
@@ -236,11 +236,11 @@ INSERT INTO {{.Table.Name}} (
236236
-- Executes the insert once for each provided set of values and returns inserted rows.
237237
-- name: {{$query_name}} :batchone
238238
INSERT INTO {{.Table.Name}} (
239-
{{ range $i, $column := .Table.Columns}}{{if $i}},
239+
{{ range $i, $column := insert_columns $}}{{if $i}},
240240
{{end}} {{$column.Name}}
241241
{{- end}}
242242
) VALUES (
243-
{{ range $i, $column := .Table.Columns}}{{if $i}},
243+
{{ range $i, $column := insert_columns $}}{{if $i}},
244244
{{end}} {{query_argument $column}}
245245
{{- end}}
246246
)
@@ -254,11 +254,11 @@ RETURNING *;
254254
-- Executes the insert once for each provided set of values and returns number of affected rows.
255255
-- name: {{$query_name}} :batchexec
256256
INSERT INTO {{.Table.Name}} (
257-
{{ range $i, $column := .Table.Columns}}{{if $i}},
257+
{{ range $i, $column := insert_columns $}}{{if $i}},
258258
{{end}} {{$column.Name}}
259259
{{- end}}
260260
) VALUES (
261-
{{ range $i, $column := .Table.Columns}}{{if $i}},
261+
{{ range $i, $column := insert_columns $}}{{if $i}},
262262
{{end}} {{query_argument $column}}
263263
{{- end}}
264264
);
@@ -270,11 +270,11 @@ INSERT INTO {{.Table.Name}} (
270270
-- This is the fastest way to insert large amounts of data. Does not return inserted rows.
271271
-- name: {{$query_name}} :copyfrom
272272
INSERT INTO {{.Table.Name}} (
273-
{{ range $i, $column := .Table.Columns}}{{if $i}},
273+
{{ range $i, $column := insert_columns $}}{{if $i}},
274274
{{end}} {{$column.Name}}
275275
{{- end}}
276276
) VALUES (
277-
{{ range $i, $column := .Table.Columns}}{{if $i}},
277+
{{ range $i, $column := insert_columns $}}{{if $i}},
278278
{{end}} {{query_argument $column}}
279279
{{- end}}
280280
);

internal/sqlc/template/template_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var _ = Describe("Template", func() {
2727
"is_fk_index": func(args ...any) bool { return false },
2828
// Query selection function
2929
"should_generate": func(args ...any) bool { return false },
30+
// Insert column filtering
31+
"insert_columns": func(args ...any) []any { return nil },
3032
}
3133

3234
It("opens and parses template successfully", func() {

0 commit comments

Comments
 (0)