Repository: sqlc-dev/sqlc
Pull request #3308 (2024-04-01 19:08:17)

feat(golang): Add initialisms configuration
Fixes https://github.com/sqlc-dev/sqlc/issues/2161


Patch:
diff --git a/docs/reference/config.md b/docs/reference/config.md
index 85e68cf05..9e9910190 100644
--- a/docs/reference/config.md
+++ b/docs/reference/config.md
@@ -167,6 +167,8 @@ The `gen` mapping supports the following keys:
   - If true, emits the SQL statement as a code-block comment above the generated function, appending to any existing comments. Defaults to `false`.
 - `build_tags`:
   - If set, add a `//go:build <build_tags>` directive at the beginning of each generated Go file.
+- `initialisms`:
+  - An array of [initialisms](https://google.github.io/styleguide/go/decisions.html#initialisms) to upper-case. For example, `app_id` becomes `AppID`. Defaults to `["id"]`.
 - `json_tags_id_uppercase`:
   - If true, "Id" in json tags will be uppercase. If false, will be camelcase. Defaults to `false`
 - `json_tags_case_style`:
diff --git a/internal/codegen/golang/opts/options.go b/internal/codegen/golang/opts/options.go
index 0e2a8562e..30a6c2246 100644
--- a/internal/codegen/golang/opts/options.go
+++ b/internal/codegen/golang/opts/options.go
@@ -43,6 +43,9 @@ type Options struct {
 	OmitSqlcVersion             bool              `json:"omit_sqlc_version,omitempty" yaml:"omit_sqlc_version"`
 	OmitUnusedStructs           bool              `json:"omit_unused_structs,omitempty" yaml:"omit_unused_structs"`
 	BuildTags                   string            `json:"build_tags,omitempty" yaml:"build_tags"`
+	Initialisms                 *[]string         `json:"initialisms,omitempty" yaml:"initialisms"`
+
+	InitialismsMap map[string]struct{} `json:"-" yaml:"-"`
 }
 
 type GlobalOptions struct {
@@ -111,6 +114,16 @@ func parseOpts(req *plugin.GenerateRequest) (*Options, error) {
 		*options.QueryParameterLimit = 1
 	}
 
+	if options.Initialisms == nil {
+		options.Initialisms = new([]string)
+		*options.Initialisms = []string{"id"}
+	}
+
+	options.InitialismsMap = map[string]struct{}{}
+	for _, initial := range *options.Initialisms {
+		options.InitialismsMap[initial] = struct{}{}
+	}
+
 	return &options, nil
 }
 
diff --git a/internal/codegen/golang/struct.go b/internal/codegen/golang/struct.go
index 322dd71bf..ed9311800 100644
--- a/internal/codegen/golang/struct.go
+++ b/internal/codegen/golang/struct.go
@@ -32,8 +32,8 @@ func StructName(name string, options *opts.Options) string {
 	}, name)
 
 	for _, p := range strings.Split(name, "_") {
-		if p == "id" {
-			out += "ID"
+		if _, found := options.InitialismsMap[p]; found {
+			out += strings.ToUpper(p)
 		} else {
 			out += strings.Title(p)
 		}


Test patch:
diff --git a/internal/endtoend/testdata/golang_initialisms_empty/db/db.go b/internal/endtoend/testdata/golang_initialisms_empty/db/db.go
new file mode 100644
index 000000000..17d86e930
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_empty/db/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.26.0
+
+package db
+
+import (
+	"context"
+	"database/sql"
+)
+
+type DBTX interface {
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/golang_initialisms_empty/db/models.go b/internal/endtoend/testdata/golang_initialisms_empty/db/models.go
new file mode 100644
index 000000000..e442271ec
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_empty/db/models.go
@@ -0,0 +1,13 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.26.0
+
+package db
+
+import (
+	"database/sql"
+)
+
+type Foo struct {
+	BarId sql.NullString
+}
diff --git a/internal/endtoend/testdata/golang_initialisms_empty/db/query.sql.go b/internal/endtoend/testdata/golang_initialisms_empty/db/query.sql.go
new file mode 100644
index 000000000..a36ef225d
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_empty/db/query.sql.go
@@ -0,0 +1,38 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.26.0
+// source: query.sql
+
+package db
+
+import (
+	"context"
+	"database/sql"
+)
+
+const selectFoo = `-- name: SelectFoo :many
+SELECT bar_id FROM foo
+`
+
+func (q *Queries) SelectFoo(ctx context.Context) ([]sql.NullString, error) {
+	rows, err := q.db.QueryContext(ctx, selectFoo)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []sql.NullString
+	for rows.Next() {
+		var bar_id sql.NullString
+		if err := rows.Scan(&bar_id); err != nil {
+			return nil, err
+		}
+		items = append(items, bar_id)
+	}
+	if err := rows.Close(); err != nil {
+		return nil, err
+	}
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return items, nil
+}
diff --git a/internal/endtoend/testdata/golang_initialisms_empty/query.sql b/internal/endtoend/testdata/golang_initialisms_empty/query.sql
new file mode 100644
index 000000000..e32e926b3
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_empty/query.sql
@@ -0,0 +1,2 @@
+-- name: SelectFoo :many
+SELECT * FROM foo;
diff --git a/internal/endtoend/testdata/golang_initialisms_empty/schema.sql b/internal/endtoend/testdata/golang_initialisms_empty/schema.sql
new file mode 100644
index 000000000..fe692b6af
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_empty/schema.sql
@@ -0,0 +1,3 @@
+CREATE TABLE foo(
+        bar_id text
+);
diff --git a/internal/endtoend/testdata/golang_initialisms_empty/sqlc.json b/internal/endtoend/testdata/golang_initialisms_empty/sqlc.json
new file mode 100644
index 000000000..02ab2e395
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_empty/sqlc.json
@@ -0,0 +1,16 @@
+{
+    "version": "2",
+    "sql": [
+        {
+            "schema": "schema.sql",
+            "queries": "query.sql",
+            "engine": "postgresql",
+            "gen": {
+                "go": {
+                    "out": "db",
+                    "initialisms": []
+                }
+            }
+        }
+    ]
+}
diff --git a/internal/endtoend/testdata/golang_initialisms_url/db/db.go b/internal/endtoend/testdata/golang_initialisms_url/db/db.go
new file mode 100644
index 000000000..17d86e930
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_url/db/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.26.0
+
+package db
+
+import (
+	"context"
+	"database/sql"
+)
+
+type DBTX interface {
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/golang_initialisms_url/db/models.go b/internal/endtoend/testdata/golang_initialisms_url/db/models.go
new file mode 100644
index 000000000..63087f994
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_url/db/models.go
@@ -0,0 +1,14 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.26.0
+
+package db
+
+import (
+	"database/sql"
+)
+
+type Foo struct {
+	BarID   sql.NullString
+	SiteURL sql.NullString
+}
diff --git a/internal/endtoend/testdata/golang_initialisms_url/db/query.sql.go b/internal/endtoend/testdata/golang_initialisms_url/db/query.sql.go
new file mode 100644
index 000000000..86f3251b2
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_url/db/query.sql.go
@@ -0,0 +1,37 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.26.0
+// source: query.sql
+
+package db
+
+import (
+	"context"
+)
+
+const selectFoo = `-- name: SelectFoo :many
+SELECT bar_id, site_url FROM foo
+`
+
+func (q *Queries) SelectFoo(ctx context.Context) ([]Foo, error) {
+	rows, err := q.db.QueryContext(ctx, selectFoo)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []Foo
+	for rows.Next() {
+		var i Foo
+		if err := rows.Scan(&i.BarID, &i.SiteURL); err != nil {
+			return nil, err
+		}
+		items = append(items, i)
+	}
+	if err := rows.Close(); err != nil {
+		return nil, err
+	}
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return items, nil
+}
diff --git a/internal/endtoend/testdata/golang_initialisms_url/query.sql b/internal/endtoend/testdata/golang_initialisms_url/query.sql
new file mode 100644
index 000000000..e32e926b3
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_url/query.sql
@@ -0,0 +1,2 @@
+-- name: SelectFoo :many
+SELECT * FROM foo;
diff --git a/internal/endtoend/testdata/golang_initialisms_url/schema.sql b/internal/endtoend/testdata/golang_initialisms_url/schema.sql
new file mode 100644
index 000000000..83ab16012
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_url/schema.sql
@@ -0,0 +1,4 @@
+CREATE TABLE foo(
+        bar_id text,
+        site_url text
+);
diff --git a/internal/endtoend/testdata/golang_initialisms_url/sqlc.json b/internal/endtoend/testdata/golang_initialisms_url/sqlc.json
new file mode 100644
index 000000000..2b135eec3
--- /dev/null
+++ b/internal/endtoend/testdata/golang_initialisms_url/sqlc.json
@@ -0,0 +1,16 @@
+{
+    "version": "2",
+    "sql": [
+        {
+            "schema": "schema.sql",
+            "queries": "query.sql",
+            "engine": "postgresql",
+            "gen": {
+                "go": {
+                    "out": "db",
+                    "initialisms": ["id", "url"]
+                }
+            }
+        }
+    ]
+}