Skip to content

Filter global type overrides by engine before generating - #4628

Open
methakon wants to merge 1 commit into
sqlc-dev:mainfrom
methakon:fix/4624-filter-global-overrides-by-engine
Open

methakon wants to merge 1 commit into
sqlc-dev:mainfrom
methakon:fix/4624-filter-global-overrides-by-engine

Conversation

@methakon

@methakon methakon commented Oct 1, 2026

Copy link
Copy Markdown

Filter global type overrides by engine before generating

Fixes #4624.

A global override that sets engine was applied to every generated package, not just the one being generated.

opts.Parse prepends the global overrides to the per-package list, and goInnerType returns on the first match. So with postgresql, mysql and sqlite declared in one config, all three packages picked up whichever db_type: text rule was listed first, and all three took the last matching go_struct_tag.

Reproduced

Using the config from the report against bdbe55db:

engine before after expected
postgresql Value string / Code json.RawMessage, tag backend:"sqlite" Value string / Code json.RawMessage, tag backend:"postgresql" ✅
mysql Value string / Code json.RawMessage, tag backend:"sqlite" Value sql.NullString / Code []byte, tag backend:"mysql" ✅
sqlite Value string / Code json.RawMessage, tag backend:"sqlite" Value []byte / Code string, tag backend:"sqlite" ✅

sqlc generate exits 0 in both cases, so the wrong types are silent.

The fix

parseGlobalOpts drops overrides whose engine is set to something other than the engine being generated. Overrides with no engine still apply everywhere, which is what single-engine configs rely on.

engine := req.Settings.GetEngine()
if engine != "" {
	filtered := make([]Override, 0, len(options.Overrides))
	for _, override := range options.Overrides {
		if override.Engine != "" && override.Engine != engine {
			continue
		}
		filtered = append(filtered, override)
	}
	options.Overrides = filtered
}

req.Settings.Engine is already what goInnerType switches on to pick the engine's own type mapping, so the selector is available at this point without any plumbing. The config layer already requires engine on global overrides whenever more than one engine is in use, so this filter never has to guess.

Tests

Adds internal/endtoend/testdata/overrides_global_engine, which generates three packages from a single config and pins every type and struct tag. Reverting options.go makes it fail with the sqlite types in all three packages:

- 		Value sql.NullString `backend:"mysql"`
+ 		Value string          `backend:"sqlite"`

go test ./internal/endtoend -run 'TestReplay/base' passes (251s), as do ./internal/codegen/golang/... and ./internal/config/.... gofmt and go vet are clean.

Fixes sqlc-dev#4624.

A global override that sets `engine` was applied to every generated package,
not just the one being generated. `opts.Parse` prepends the global overrides to
the per-package list, and the first match wins in goInnerType, so with
postgresql, mysql and sqlite declared in one config all three packages picked up
whichever rule was listed first and, for struct tags, the last one.

Reproduced with the config from the report on bdbe55d:

```
                 before                              after
postgresql  Value string   backend:"sqlite"     Value string          backend:"postgresql"
            Code  json.RawMessage                Code  json.RawMessage
mysql       Value string   backend:"sqlite"     Value sql.NullString  backend:"mysql"
            Code  json.RawMessage                Code  []byte
sqlite      Value string   backend:"sqlite"     Value []byte          backend:"sqlite"
            Code  json.RawMessage                Code  string
```

parseGlobalOpts now drops overrides whose `engine` is set to something other
than the engine being generated. Overrides with no `engine` keep applying to
every engine, which is what single-engine configs use.

The config already requires `engine` on global overrides whenever more than one
engine is in use (internal/config), so the selector is always available at the
point this filter runs.

Adds internal/endtoend/testdata/overrides_global_engine, which generates three
packages from one config and pins each type and struct tag. Reverting
options.go makes it fail with the sqlite types in all three packages.

go test ./internal/codegen/golang/... ./internal/config/... passes. gofmt and go
vet are clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Global Go type overrides ignore the engine selector

1 participant