Skip to content

feat(codegen/golang): Allow exporting models to a different package - #3874

Closed
berk-karaal wants to merge 3 commits into
sqlc-dev:mainfrom
berk-karaal:main
Closed

feat(codegen/golang): Allow exporting models to a different package#3874
berk-karaal wants to merge 3 commits into
sqlc-dev:mainfrom
berk-karaal:main

Conversation

@berk-karaal

Copy link
Copy Markdown

Fixes #835

This PR adds exporting models to a different package feature to Go code generator.

I also added a how-to page for this feature, you can check the docs/howto/separate-models-file.md file.

Main code changes made in this PR

  • Added optional output_models_package, models_package_import_path and output_query_files_directory options to gen/go configuration. (internal/codegen/golang/opts/options.go)
  • Added Package field to Struct type to specify the correct type of the Struct since they can be in different package now. (internal/codegen/golang/struct.go and internal/codegen/golang/result.go)
  • Type() method of QueryValue will return {Package}.{Name} for Struct types if the Package field of the Struct is not empty. (internal/codegen/golang/query.go)
  • Added {Package}. prefix to enum types if configuration specifies separate models package. (changed only internal/codegen/golang/postgresql_type.go since only postgresql implementation supports typed enum values)
  • Deciding whether the generated file needs to import the separated models file package. (internal/codegen/golang/imports.go)

Note for sqlc users

You can simply try this feature using my sqlc-gen-go plugin fork. I will try to keep that plugin and this PR in sync. Surely sqlc-gen-go plugin configuration is not same as the sqlc gen configuration but the feature implementation code is the same as this PR.

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. 🔧 golang labels Mar 6, 2025
dimkaufo added a commit to dimkaufo/sqlc that referenced this pull request May 26, 2025
@nicklasos

Copy link
Copy Markdown

Any plans to merge this PR? It’s a really useful feature for large projects using sqlc.

@pgmitche

Copy link
Copy Markdown

Would love to see some movement on this one, pretty handy feature. What's keeping this from being integrated?

@nicklasos

Copy link
Copy Markdown

@kyleconroy hey! I’m working on a big project using SQLC, and it keeps growing. This PR would have a huge positive impact on my project as well as many others. From my perspective, this is the only weak spot in SQLC, and it could be resolved with this PR. I already tested this PR on our codebase, and it works like a charm. Please consider reviewing it when you have a chance, and please let me know if I can help in any way. Thank you!

@nikitamarchenko

Copy link
Copy Markdown

I need this feature. Can't use without. Please.

@vague2k

vague2k commented Nov 10, 2025

Copy link
Copy Markdown

patiently waiting as well :)

@thelovekesh

Copy link
Copy Markdown

@kyleconroy any plans to merge this?

@S1riyS

S1riyS commented Feb 13, 2026

Copy link
Copy Markdown

@kyleconroy it seems like a great feature. Would be grateful if this PR was merged!

@kyleconroy kyleconroy closed this May 5, 2026
@kyleconroy

Copy link
Copy Markdown
Collaborator

Fixed here #4421

mikethicke added a commit to Epistemic-Technology/beta-reader that referenced this pull request Aug 7, 2026
internal/store held its SQL as string literals, its column order as a const
per aggregate (authorColumns, sessionColumns, apiTokenColumns), and its
scanning as a hand-written scan* function matching that const positionally.
Nothing checked the three against each other or against the schema. At four
columns that is fine. The schema ARCHITECTURE §2 already describes has
releases at eighteen, where a positional Scan drifting from a select list
fails silently — which is the failure those consts exist to make unlikely
without being able to make it impossible.

sqlc makes it impossible: queries are checked against the migrations at
generation time, and CI fails on drift. Adopted now, at three tables rather
than at eleven, because every phase from 2 onward adds schema and this is
the cheapest the migration will ever be.

internal/domain is now generated. sqlc emits row structs and query methods
into one package, and the upstream option to split them was proposed and
closed unmerged (sqlc-dev/sqlc#3874), so the package depends on pgx and
ARCHITECTURE §1's "domain depends on nothing" rule is gone. The alternative
was a parallel struct set and a converter per table — the duplication this
change exists to remove — and paying it to keep a layering rule would have
been paying twice. The generated Author, Session and APIToken came out
field-for-field identical to the hand-written types they replace, so nothing
above the store moved.

What sqlc does not do stays in store: ErrNotFound and ErrUsernameTaken, the
constraint-name unique-violation mapping, and the ownership scoping that
makes the §5 authorization rules true. requireRow now takes a count from
:execrows rather than a CommandTag, which has a pleasant side effect — the
zero-rows policy is stated in the query file's annotation. DeleteAPIToken is
:execrows because another author's id must read as ErrNotFound;
DeleteSessionsForAuthor is :exec because an author with no live session has
nothing to revoke. That distinction used to live only in a comment.

Four things the config has to carry, all found by running sqlc rather than
by reading its documentation. citext without an override generates as
interface{} — silently, not as an error. emit_pointers_for_null_types does
not cover timestamps: without an explicit override every timestamp column,
nullable or not, is pgtype.Timestamptz, and that type spreads to every
caller. Nullable enums are *CommentState as hoped. And sqlc's initialisms
need rename:, keyed on the singular table name, because the plural silently
does nothing. Each override is keyed on both spellings of its type, bare and
canonical, since an override matching nothing fails that same silent way.

internal/db/schema.sql and the just db-schema target that produced it are
deleted. sqlc parses the goose migrations directly and produces
byte-identical output, so the snapshot had no consumer — and it was the one
drift CI could not catch: a migration merged without re-running db-schema
would leave sqlc diff passing against a stale snapshot, with every select *
expansion quietly omitting the new column. The migrations are now the only
schema artifact, and just generate needs neither Docker nor Postgres.

The store and HTTP suites pass with no test file touched, which is the whole
claim a behaviour-preserving retrofit can make.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files. 🔧 golang

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export models to a different package

8 participants