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/ast node tree.
  • Implementations:
    • internal/engine/postgresql — uses pg_query_go C bindings
    • internal/engine/dolphin — uses TiDB’s parser for MySQL
    • internal/engine/sqlite — uses an internal ANTLR-based parser
    • internal/engine/clickhouse — partial implementation
    • internal/x/expander — a variant Parser for 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 Engine hints 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 Analyzer that can execute PREPARE statements against a real database and retrieve exact column types.
  • Implementations:
    • internal/engine/postgresql/analyzer — uses pgx (PostgreSQL)
    • internal/engine/sqlite/analyzer — uses database/sql (SQLite)
    • internal/analyzer.CachedAnalyzer — a transparent decorator that caches results in FNV-hashed files on disk using protobuf serialization
  • Design quality: Well-designed. EnsureConn and GetColumnNames are the “database-only mode” additions that sit cleanly alongside the primary Analyze method. The decorator pattern via CachedAnalyzer is 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.ClientConnInterface that adds a type-safe Generate method 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 plain func(context.Context, *plugin.GenerateRequest) (*plugin.GenerateResponse, error) (built-in generators like golang.Generate, json.Generate)
    • ext/wasm.Runner — executes a WASM module via the Wazero runtime; its Invoke method serializes the request as protobuf and feeds it to the WASM guest’s _start function
    • ext/process.Runner — spawns a subprocess and sends the serialized protobuf request over stdin, reads response from stdout
  • Design quality: Creative adapter design. The grpc.ClientConnInterface embedding is intentional: plugin.NewCodegenServiceClient(handler) returns a strongly-typed gRPC client backed by any of the three implementations. The Generate helper method is convenience sugar over Invoke. The only tension is that NewStream always returns codes.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 Pairs method enumerates (sql queryset × generator) combinations) from what to do with the result (the ProcessResult method handles post-compilation output). This lets sqlc generate and sqlc vet share the same parallel orchestration loop (processQuerySets) while differing only in how they consume the compiler output.
  • Implementations:
    • generator (in internal/cmd/generate.go) — calls codegen() and writes files to the output map
    • vetter (in internal/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 Pairs method could arguably be split out into a separate interface, but given that both generate and vet need 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 vet query execution or star-expansion rewrites), it must use the correct parameter placeholder ($1 vs ?), 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, the Dialect interface is also referenced there).
  • Design quality: Well-segregated, five focused methods. Each corresponds to a concrete syntactic difference between SQL dialects. The Cast method is the most interesting—it encapsulates expr::type (PostgreSQL) vs CAST(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.) implements Node, enabling uniform traversal via astutils.Walk and astutils.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 external astutils code 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 CodegenService gRPC service. This is the actual interface called by the codegen dispatch loop — plugin.NewCodegenServiceClient(handler).Generate(ctx, req) — where handler is any grpc.ClientConnInterface implementation (built-in, WASM, or process).
  • Implementations: codegenServiceClient (generated, wraps grpc.ClientConnInterface.Invoke)
  • Design quality: Single-method interface; as minimal as it gets. The companion CodegenServiceServer interface (with mustEmbed… forward-compatibility guard) follows the standard protobuf-go pattern.

Interface patterns#

  • Size distribution: Very lean. ast.Node has 1 method; plugin.CodegenServiceClient has 1; compiler.Parser has 3; format.Dialect has 5. The largest hand-authored interface is analyzer.Analyzer with 4 methods. No “God interface” anti-patterns.
  • Embedding: ext.Handler intentionally embeds grpc.ClientConnInterface (itself an interface with Invoke and NewStream). This is the project’s single notable interface-embedding case and it is load-bearing: it lets all three plugin backends be used directly wherever grpc.ClientConnInterface is expected without any extra wrapping.
  • Implicit satisfaction: All interfaces are defined by consumers (Go duck typing). compiler.Parser is defined in the compiler package, not in any engine package; engines satisfy it implicitly. ext.Handler is defined in ext, not in wasm or process. This is the canonical consumer-defines-interface pattern.
  • stdlib interfaces used:
    • io.Readercompiler.Parser.Parse accepts an io.Reader, enabling parsing from files, strings, or any stream
    • context.Context — present in all I/O-adjacent interfaces (Analyzer, Handler, ResultProcessor)
    • No fmt.Stringer, sort.Interface, or io.Writer implementations found in the core pipeline interfaces; stdlib interfaces appear in individual AST node Format methods (write to a TrackedBuffer).

Key abstractions#

  1. 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.

  2. grpc.ClientConnInterface / ext.Handler — The plugin dispatch abstraction. By expressing all three codegen backends (in-process, WASM, subprocess) as grpc.ClientConnInterface implementations, 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.

  3. analyzer.Analyzer — The live-database escape hatch. Static analysis has limits; this interface lets the compiler fall back to a real database when needed. CachedAnalyzer shows decorator pattern at its most practical: zero changes to the core Analyzer contract, transparent caching added by wrapping.

  4. cmd.ResultProcessor — The strategy interface that makes sqlc generate and sqlc vet share infrastructure. The parallel query-set processing loop is identical for both commands; only the ProcessResult implementation differs. This is textbook strategy pattern applied to a compiler workflow.

  5. 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.