sqlc — Interfaces#
Interface catalog#
Parser#
- Package:
github.com/sqlc-dev/sqlc/internal/compiler - File:
internal/compiler/compile.go:23 - Methods:
Parse(io.Reader) ([]ast.Statement, error) CommentSyntax() source.CommentSyntax IsReservedKeyword(string) bool - Purpose: Defines the contract for a SQL dialect parser. Each engine (PostgreSQL, MySQL/Dolphin, SQLite, ClickHouse) implements this interface to convert raw SQL text into the shared
sql/astnode tree. - Implementations:
internal/engine/postgresql— usespg_query_goC bindingsinternal/engine/dolphin— uses TiDB’s parser for MySQLinternal/engine/sqlite— uses an internal ANTLR-based parserinternal/engine/clickhouse— partial implementationinternal/x/expander— a variantParserfor star-expansion rewrites
- Design quality: Excellent ISP compliance. Three methods, each serving a distinct purpose: parsing SQL text, reporting how comments are handled (for query name/annotation extraction), and keyword lookup. Very tight scope; adding a new dialect means implementing exactly this contract and nothing else. The comment
// TODO: Rename this interface Enginehints at a future rename to better reflect that it represents a whole dialect, not just parsing.
Analyzer#
- Package:
github.com/sqlc-dev/sqlc/internal/analyzer - File:
internal/analyzer/analyzer.go:121 - Methods:
Analyze(context.Context, ast.Node, string, []string, *named.ParamSet) (*analysis.Analysis, error) Close(context.Context) error EnsureConn(ctx context.Context, migrations []string) error GetColumnNames(ctx context.Context, query string) ([]string, error) - Purpose: Abstracts a live-database connection used for type analysis. When static catalog analysis is insufficient (e.g., complex expressions, star expansion), the compiler delegates to an
Analyzerthat can executePREPAREstatements against a real database and retrieve exact column types. - Implementations:
internal/engine/postgresql/analyzer— usespgx(PostgreSQL)internal/engine/sqlite/analyzer— usesdatabase/sql(SQLite)internal/analyzer.CachedAnalyzer— a transparent decorator that caches results in FNV-hashed files on disk using protobuf serialization
- Design quality: Well-designed.
EnsureConnandGetColumnNamesare the “database-only mode” additions that sit cleanly alongside the primaryAnalyzemethod. The decorator pattern viaCachedAnalyzeris the canonical way to add caching without polluting concrete implementations. Four methods is on the edge of ISP, but each is genuinely needed to support the full database-backed analysis feature.
ext.Handler#
- Package:
github.com/sqlc-dev/sqlc/internal/ext - File:
internal/ext/handler.go:14 - Methods:
Generate(context.Context, *plugin.GenerateRequest) (*plugin.GenerateResponse, error) // Embeds grpc.ClientConnInterface: Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) - Purpose: A superset of
grpc.ClientConnInterfacethat adds a type-safeGeneratemethod on top. It is the unified codegen dispatch abstraction: built-in Go generators, WASM plugins, and subprocess plugins all implement this interface, making the codegen dispatch loop uniform. - Implementations:
ext.wrapper— adapts a plainfunc(context.Context, *plugin.GenerateRequest) (*plugin.GenerateResponse, error)(built-in generators likegolang.Generate,json.Generate)ext/wasm.Runner— executes a WASM module via the Wazero runtime; itsInvokemethod serializes the request as protobuf and feeds it to the WASM guest’s_startfunctionext/process.Runner— spawns a subprocess and sends the serialized protobuf request over stdin, reads response from stdout
- Design quality: Creative adapter design. The
grpc.ClientConnInterfaceembedding is intentional:plugin.NewCodegenServiceClient(handler)returns a strongly-typed gRPC client backed by any of the three implementations. TheGeneratehelper method is convenience sugar overInvoke. The only tension is thatNewStreamalways returnscodes.Unimplemented— streaming is structurally excluded, which is fine for this use case.
ResultProcessor#
- Package:
github.com/sqlc-dev/sqlc/internal/cmd - File:
internal/cmd/process.go:27 - Methods:
Pairs(context.Context, *config.Config) []OutputPair ProcessResult(context.Context, config.CombinedSettings, OutputPair, *compiler.Result) error - Purpose: Separates what to generate (the
Pairsmethod enumerates (sql queryset × generator) combinations) from what to do with the result (theProcessResultmethod handles post-compilation output). This letssqlc generateandsqlc vetshare the same parallel orchestration loop (processQuerySets) while differing only in how they consume the compiler output. - Implementations:
generator(ininternal/cmd/generate.go) — callscodegen()and writes files to the output mapvetter(ininternal/cmd/vet.go) — runs CEL rule evaluation against each compiled query
- Design quality: Clean strategy pattern. Two implementations, two methods each serving its part of the strategy contract. The
Pairsmethod could arguably be split out into a separate interface, but given that bothgenerateandvetneed custom pair enumeration (vet adds live-DB pairs), keeping them together is pragmatic.
format.Dialect#
- Package:
github.com/sqlc-dev/sqlc/internal/sql/format - File:
internal/sql/format/format.go:4 - Methods:
QuoteIdent(s string) string TypeName(ns, name string) string Param(n int) string NamedParam(name string) string Cast(arg, typeName string) string - Purpose: Provides dialect-specific SQL rendering rules. When sqlc re-serializes AST nodes back to SQL text (e.g. for
sqlc vetquery execution or star-expansion rewrites), it must use the correct parameter placeholder ($1vs?), quoting rules, and type name formats. This interface is the single seam for those differences. - Implementations: One per supported SQL dialect (PostgreSQL, MySQL/Dolphin, SQLite); implementations live in
internal/sql/ast(per the CLAUDE.md in that package, theDialectinterface is also referenced there). - Design quality: Well-segregated, five focused methods. Each corresponds to a concrete syntactic difference between SQL dialects. The
Castmethod is the most interesting—it encapsulatesexpr::type(PostgreSQL) vsCAST(expr AS type)(MySQL/SQLite) without exposing any AST detail to callers.
ast.Node#
- Package:
github.com/sqlc-dev/sqlc/internal/sql/ast - File:
internal/sql/ast/node.go:3 - Methods:
Pos() int - Purpose: The root marker interface for all AST nodes. Every SQL AST type (
SelectStmt,InsertStmt,ColumnRef,FuncCall, etc.) implementsNode, enabling uniform traversal viaastutils.Walkandastutils.Apply, and source-position reporting for error messages. - Implementations: ~50+ concrete types in
internal/sql/ast/ - Design quality: Intentionally minimal — a single
Pos()method. Walk/Apply traversal operates through type switches rather than a visitor method on the interface itself. This is idiomatic Go: a narrow interface provides the essential type identity while keeping the AST nodes simple structs. The only concern is that AST traversal logic must live in externalastutilscode rather than being encapsulated.
plugin.CodegenServiceClient (generated)#
- Package:
github.com/sqlc-dev/sqlc/internal/plugin - File:
internal/plugin/codegen_grpc.pb.go:28 - Methods:
Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (*GenerateResponse, error) - Purpose: The protobuf-generated client interface for the
CodegenServicegRPC service. This is the actual interface called by the codegen dispatch loop —plugin.NewCodegenServiceClient(handler).Generate(ctx, req)— wherehandleris anygrpc.ClientConnInterfaceimplementation (built-in, WASM, or process). - Implementations:
codegenServiceClient(generated, wrapsgrpc.ClientConnInterface.Invoke) - Design quality: Single-method interface; as minimal as it gets. The companion
CodegenServiceServerinterface (withmustEmbed…forward-compatibility guard) follows the standard protobuf-go pattern.
Interface patterns#
- Size distribution: Very lean.
ast.Nodehas 1 method;plugin.CodegenServiceClienthas 1;compiler.Parserhas 3;format.Dialecthas 5. The largest hand-authored interface isanalyzer.Analyzerwith 4 methods. No “God interface” anti-patterns. - Embedding:
ext.Handlerintentionally embedsgrpc.ClientConnInterface(itself an interface withInvokeandNewStream). This is the project’s single notable interface-embedding case and it is load-bearing: it lets all three plugin backends be used directly wherevergrpc.ClientConnInterfaceis expected without any extra wrapping. - Implicit satisfaction: All interfaces are defined by consumers (Go duck typing).
compiler.Parseris defined in thecompilerpackage, not in any engine package; engines satisfy it implicitly.ext.Handleris defined inext, not inwasmorprocess. This is the canonical consumer-defines-interface pattern. - stdlib interfaces used:
io.Reader—compiler.Parser.Parseaccepts anio.Reader, enabling parsing from files, strings, or any streamcontext.Context— present in all I/O-adjacent interfaces (Analyzer,Handler,ResultProcessor)- No
fmt.Stringer,sort.Interface, orio.Writerimplementations found in the core pipeline interfaces; stdlib interfaces appear in individual AST nodeFormatmethods (write to aTrackedBuffer).
Key abstractions#
compiler.Parser— The primary extensibility seam for SQL dialects. Every new database engine reduces to implementing 3 methods. Its simplicity is why adding ClickHouse support required only a new package, not touching the compiler.grpc.ClientConnInterface/ext.Handler— The plugin dispatch abstraction. By expressing all three codegen backends (in-process, WASM, subprocess) asgrpc.ClientConnInterfaceimplementations, the orchestration loop is reduced to a single line regardless of where the code generator runs. This is the most architecturally distinctive decision in the codebase.analyzer.Analyzer— The live-database escape hatch. Static analysis has limits; this interface lets the compiler fall back to a real database when needed.CachedAnalyzershows decorator pattern at its most practical: zero changes to the coreAnalyzercontract, transparent caching added by wrapping.cmd.ResultProcessor— The strategy interface that makessqlc generateandsqlc vetshare infrastructure. The parallel query-set processing loop is identical for both commands; only theProcessResultimplementation differs. This is textbook strategy pattern applied to a compiler workflow.format.Dialect— The SQL rendering seam. Often overlooked, but necessary: sqlc’s ability to re-emit type-correct SQL (for PREPARE statements, star expansion, etc.) without hardcoding per-dialect branches throughout the codebase depends on this interface being the single point of variation.
Interface-driven extensibility#
sqlc’s extensibility model has two distinct layers:
SQL dialect extensibility is via compiler.Parser + format.Dialect. Adding a new database engine means: (a) implement Parser to convert the dialect’s parse tree to sql/ast, (b) implement Dialect for query re-rendering. Nothing else in the pipeline needs to change.
Code generator extensibility is via grpc.ClientConnInterface (through the ext.Handler abstraction). A third-party code generator needs only to: (a) accept a serialized plugin.GenerateRequest protobuf on stdin, (b) write a serialized plugin.GenerateResponse protobuf to stdout (process plugin), or (c) export a WASM module that does the same. The sqlc.yaml plugins: section wires it in. No Go code needs to be contributed to the sqlc repository itself.
This two-layer design means that dialect support and language/framework support are independently extensible — a WASM plugin author doesn’t need to know how the PostgreSQL parser works, and a dialect maintainer doesn’t need to know what code generators exist.