Skip to content

feat(plugin): expose domains and resolve Go domain types - #4688

Draft
EliSauder wants to merge 15 commits into
sqlc-dev:mainfrom
EliSauder:plugin-domains
Draft

EliSauder wants to merge 15 commits into
sqlc-dev:mainfrom
EliSauder:plugin-domains

Conversation

@EliSauder

@EliSauder EliSauder commented Nov 13, 2026 •

Copy link
Copy Markdown

Summary

Expose domain and alias-type definitions to code-generation plugins, and use that metadata to generate Go fields as their base types instead of any.

A column still reports its domain's own type name. The plugin catalog gains an additive Schema.domains field describing each domain's name, base type expression, nullability constraint, and comment. This preserves the analysis core's distinction between a domain and its base while giving generators enough information to choose a representation.

  • Populate domains from the core catalog, including SQL Server CREATE TYPE ... FROM aliases.
  • Record PostgreSQL CREATE/DROP DOMAIN and COMMENT ON DOMAIN on the default analysis path.
  • Resolve Go domain types one step at a time, retaining overrides on the domain or an intermediate base domain, array dimensions, and base-type override struct tags.
  • Track domain-imposed nullability separately from a column's own constraint, including ALTER DOMAIN SET/DROP NOT NULL, nested domains, and inherited columns.

Related requests and design context: #4483, #434, #3324, #3265, #4618, and #4621.

Compatibility and generated changes

The protocol change is additive. Existing plugins that do not read domains retain their current behavior; external generators need regenerated protocol bindings to use the new metadata. Column type names are not replaced with base names in the plugin request. The protobuf bindings and catalog query output are included.

Existing JSON request goldens gain domains; SQL Server alias columns previously represented as any become their base representations. The new PostgreSQL request fixture is large because it records the complete catalog, following the existing request-dump tests.

This branch targets main directly and is independent of the SQL Server query and expression-typing proposals.

Related independent drafts: #4687 (core query support) and #4689 (expression typing). Adjacent compiler edits may need conflict resolution when these are integrated; none requires another to land first.

Tests and validation

New CLI golden cases cover PostgreSQL and SQL Server plugin requests, Go domain representations, nullability changes, inherited constraints, arrays, override precedence, intermediate domains, and domain comments. Override documentation is updated.

The complete TestReplay/base/ corpus passed during preparation on this branch's unchanged code tree. Existing golden differences are limited to the intended metadata, alias-type representations, and corrected inherited-column nullability.

This draft submission did not rerun the full live-database/examples suite. Upstream CI has not yet been evaluated.

Known limitations

  • View-column nullability is not recomputed after ALTER DOMAIN SET/DROP NOT NULL.
  • A domain shadowing a built-in type can still be confused with that type.
  • Domain rename and base-type rename/schema-transfer handling remain incomplete.
  • A domain in a default schema uses the existing bare-name override convention; explicitly qualified default-schema overrides are not added here.

Fork review and detailed history: EliSauder#2.

AI assistance

Prepared with GitHub Copilot assistance. Opened as a draft for author and maintainer review.

EliSauder and others added 15 commits November 12, 2026 13:28
A column typed with a domain reports the domain's name, as the analysis
core and the databases themselves do, but nothing told a plugin what
the domain stands on, so generators fell back to an untyped value. The
plugin catalog's Schema gains domains: each domain's name, its base as a
type expression, and whether it forbids NULL. The field is additive;
plugins that do not read it are unaffected.

The core path fills it from its catalog, which already records every
domain and SQL Server alias type with its base, so SQL Server's
CREATE TYPE ... FROM, and PostgreSQL's CREATE DOMAIN under the core
analyzer, reach plugins. A domain outside the default schema is named
qualified, as columns of it are.

Refs sqlc-dev#4483

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The catalog PostgreSQL is analyzed with by default ignored CREATE DOMAIN,
so a plugin generating for PostgreSQL was told nothing about a domain
even with the protocol able to carry it. The catalog now records each
domain with its base type and whether it is declared NOT NULL, in the
schema it was created in, and DROP DOMAIN removes it as DROP TYPE does.

Refs sqlc-dev#4483

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A column typed with a PostgreSQL domain or a SQL Server alias type was
generated as interface{} or any, since no mapper knew the domain's name.
The Go generator now looks the domain up in the plugin catalog and maps
the column as the domain's base, wherever the domain appears in the type,
so an array of a domain is a slice of its base. A db_type override that
names the domain still applies first, so one override covers every
column of it, as sqlc-dev#434 describes.

Refs sqlc-dev#3324
Refs sqlc-dev#434

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A column typed with a domain declared NOT NULL was still nullable, so
its Go field was a nullable wrapper for a value that can never be NULL.
A column of such a domain is now NOT NULL, as PostgreSQL enforces it;
an array of one may still be NULL, and the optional side of an outer
join is still nullable.

Fixes sqlc-dev#3265

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A type outside the default namespaces is reported qualified, so a bare
name falling back to any schema made billing.text stand in for every
ordinary text column, and recursed forever when its base was text. A
domain whose base reports its own bare name stops there too.

Addresses review comment r4556992324 on #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The PostgreSQL mappers read the flat type, so naming it after the
resolved expression made an array of a domain the type array, and
generated []any. Describe the expression the way coreResultCatalog
does: the innermost element's name and one dimension per array.

Addresses review comment r4556992392 on #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The flat type kept only the base's name, so a domain over text[] lost
its element and dimensions and generated any. Add the base's dimensions
to the column's own, and let the domain's base decide how many slices
the Go type has, since the column alone does not say.

Addresses review comment r4556992433 on #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A column took its domain's NOT NULL as its own, so changing its type
kept it, and dropping the column's NOT NULL dropped the domain's too.
Remember which one the column has, and work it out again when the
column's type or constraint changes.

Addresses review comment r4556992493 on #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The catalog ignored ALTER DOMAIN, so a domain kept the NOT NULL it was
created with, and so did its columns. Apply the change to the domain
and work out each column's again, leaving a column's own NOT NULL be.

Addresses review comment r4556992586 on #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A domain over a NOT NULL domain forbids NULL too, but only the domain's
own constraint made its columns NOT NULL. Follow the domain's base while
it is a domain, stopping at an array, whose elements' constraint does
not apply to the array itself.

Addresses review comment r4556992619 on #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The base was read the way the analysis core reads a type, lowercased
and without pg_catalog, so a domain over a quoted enum named no enum
and one over numeric missed a pg_catalog.numeric override. Keep the
parser's name, as a column of the base type has it.

Addresses review comment r4556992547 on #2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ain's

A child that redeclared an inherited domain column NOT NULL kept the
mark that the NOT NULL came from the domain alone, so ALTER DOMAIN ...
DROP NOT NULL cleared the child's own constraint. The child also shared
the parent's column, so merging a NOT NULL into it changed the parent.
Copy inherited columns and drop the domain mark when any merged
declaration is NOT NULL by its own constraint.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
An override on a domain's base type supplied the column's Go type, but
its go_struct_tag was matched only against the domain's own name and so
was dropped. Walk the domain to its base for tags the way goInnerType
does for the type, stopping where an override names the type, and
apply the domain's and column's own tags over the base's.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
COMMENT ON DOMAIN was skipped, so a plugin saw an empty comment on a
domain the schema commented. Translate it as COMMENT ON TYPE, which
already finds the domain by its type name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
resolveDomains replaced a whole chain of domains with the last base, so
goInnerType never checked an override on a domain in the middle of the
chain: with validated_id AS id AS text, a db_type override on id
generated string. Replace one domain per step, as the legacy path does,
and let goInnerType check overrides between steps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@EliSauder

Copy link
Copy Markdown
Author

I have been using sqlc with mssql as a trial and ran in to feature gaps so I figured I would spend the time and tokens to suggest some changes.

Adds the type alias support. Structs no longer resolve to any, instead resolve based on the underlying type of the alias. This is the PR I am most unsure about since it isn't a huge gap in my opinion, but is a nice to have.

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.

1 participant