From b633980b9f81257dc5cf44cc85cfc0945f940558 Mon Sep 17 00:00:00 2001 From: Wael Nasreddine Date: Mon, 23 Mar 2026 01:48:56 -0700 Subject: [PATCH 1/2] fix: correct LastInsertId and Url casing in generated code This fixes two bugs in FixAcronyms: 1. LastInsertId: The regex was incorrectly transforming res.LastInsertId() to res.LastInsertID(). The Go standard library's sql.Result interface defines LastInsertId() (lowercase d), so the generated MySQL wrappers failed to compile. 2. Url casing: With ExtraRules: true, gofumpt was uppercasing field names like NewUrl to NewURL, but the underlying struct types generated by sqlc still use NewUrl, causing type mismatches. The fix disables ExtraRules in gofumpt and modifies the Id regex to exclude '(' from the character class, preventing method calls like LastInsertId() from being transformed. --- .../pkg/database/generated_wrapper_mysql.go | 4 +-- generator/helpers.go | 12 +++++++-- generator/helpers_test.go | 25 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/example/pkg/database/generated_wrapper_mysql.go b/example/pkg/database/generated_wrapper_mysql.go index f2cf466..9c375b3 100644 --- a/example/pkg/database/generated_wrapper_mysql.go +++ b/example/pkg/database/generated_wrapper_mysql.go @@ -59,7 +59,7 @@ func (w *mysqlWrapper) CreateBook(ctx context.Context, arg CreateBookParams) (Bo return Book{}, err } - id, err := res.LastInsertID() + id, err := res.LastInsertId() if err != nil { return Book{}, err } @@ -89,7 +89,7 @@ func (w *mysqlWrapper) CreateTag(ctx context.Context, name string) (Tag, error) return Tag{}, err } - id, err := res.LastInsertID() + id, err := res.LastInsertId() if err != nil { return Tag{}, err } diff --git a/generator/helpers.go b/generator/helpers.go index 86c68af..c09a2f0 100644 --- a/generator/helpers.go +++ b/generator/helpers.go @@ -92,11 +92,19 @@ func FixAcronyms(content []byte) []byte { // 3. Mid: `([a-z])(Acronym)([A-Z])` - acronym in middle of camelCase, e.g., Id in userIdMore. // 4. End: `([a-z])(Acronym)$` - acronym at end of identifier, e.g., Id in userId. // 5. NonLetter: `([a-z])(Acronym)([^A-Za-z])` - acronym followed by non-letter. + // For Id, we exclude '(' to avoid transforming method calls like LastInsertId(). regexStart := regexp.MustCompile(`^(` + a.pattern + `)([A-Z])`) regexAfterUpper := regexp.MustCompile(`([A-Z])(` + a.pattern + `)([A-Z])`) regexMid := regexp.MustCompile(`([a-z])(` + a.pattern + `)([A-Z])`) regexEnd := regexp.MustCompile(`([a-z])(` + a.pattern + `)$`) - regexNonLetter := regexp.MustCompile(`([a-z])(` + a.pattern + `)([^A-Za-z])`) + + // For Id, exclude '(' to preserve method calls like res.LastInsertId() + nonLetterClass := `[^A-Za-z]` + if a.pattern == "Id" { + nonLetterClass = `[^A-Za-z(]` + } + + regexNonLetter := regexp.MustCompile(`([a-z])(` + a.pattern + `)(` + nonLetterClass + `)`) // Start case: replace with replacement followed by ${2} (the uppercase after). result = regexStart.ReplaceAllString(result, a.replacement+"${2}") @@ -124,7 +132,7 @@ func writeFile(dir, filename string, content []byte) { // 2. Format with gofumpt formatted, err := format.Source(withImports, format.Options{ LangVersion: "", - ExtraRules: true, + ExtraRules: false, }) if err != nil { log.Println(string(withImports)) diff --git a/generator/helpers_test.go b/generator/helpers_test.go index aebbbc2..ce4a312 100644 --- a/generator/helpers_test.go +++ b/generator/helpers_test.go @@ -109,6 +109,31 @@ func TestFixAcronyms(t *testing.T) { input: "", expected: "", }, + { + name: "LastInsertId in method call should not be transformed", + input: "res.LastInsertId()", + expected: "res.LastInsertId()", + }, + { + name: "SetId in method call should not be transformed", + input: "obj.SetId(123)", + expected: "obj.SetId(123)", + }, + { + name: "userId in variable assignment should become userID", + input: "userId = 123", + expected: "userID = 123", + }, + { + name: "imageUrl in struct literal should become imageURL", + input: "imageUrl: \"https://example.com\"", + expected: "imageURL: \"https://example.com\"", + }, + { + name: "profileUrl in assignment should become profileURL", + input: "profileUrl = \"https://example.com\"", + expected: "profileURL = \"https://example.com\"", + }, } for _, tt := range tests { From 46c4ca161d37f63d9c79993b066b4be49384784e Mon Sep 17 00:00:00 2001 From: Wael Nasreddine Date: Mon, 23 Mar 2026 10:13:40 -0700 Subject: [PATCH 2/2] fix newurl --- generator/helpers.go | 7 ++++++- generator/helpers_test.go | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/generator/helpers.go b/generator/helpers.go index c09a2f0..56b79c1 100644 --- a/generator/helpers.go +++ b/generator/helpers.go @@ -99,9 +99,14 @@ func FixAcronyms(content []byte) []byte { regexEnd := regexp.MustCompile(`([a-z])(` + a.pattern + `)$`) // For Id, exclude '(' to preserve method calls like res.LastInsertId() + // For Url, exclude ':' and ',' to preserve struct field names like NewUrl, OldUrl nonLetterClass := `[^A-Za-z]` - if a.pattern == "Id" { + + switch a.pattern { + case "Id": nonLetterClass = `[^A-Za-z(]` + case "Url": + nonLetterClass = `[^A-Za-z:,]` } regexNonLetter := regexp.MustCompile(`([a-z])(` + a.pattern + `)(` + nonLetterClass + `)`) diff --git a/generator/helpers_test.go b/generator/helpers_test.go index ce4a312..3a90822 100644 --- a/generator/helpers_test.go +++ b/generator/helpers_test.go @@ -125,9 +125,14 @@ func TestFixAcronyms(t *testing.T) { expected: "userID = 123", }, { - name: "imageUrl in struct literal should become imageURL", - input: "imageUrl: \"https://example.com\"", - expected: "imageURL: \"https://example.com\"", + name: "NewUrl in struct literal should remain NewUrl", + input: "NewUrl: \"value\"", + expected: "NewUrl: \"value\"", + }, + { + name: "OldUrl in struct literal should remain OldUrl", + input: "OldUrl: \"value\"", + expected: "OldUrl: \"value\"", }, { name: "profileUrl in assignment should become profileURL",