This document describes the DuckDB engine implementation for sqlc.
DuckDB support has been added to sqlc using a database-backed approach, similar to PostgreSQL's analyzer pattern. Unlike MySQL and SQLite which use Go-based catalogs, DuckDB relies entirely on database connections for type inference and schema information.
-
Parser (
/internal/engine/duckdb/parse.go)- Minimal pass-through parser - does not parse SQL into AST
- All parsing and validation happens in the database via the analyzer
- Implements the
Parserinterface withParse(),CommentSyntax(), andIsReservedKeyword()methods - Supports
--and/* */comment styles (DuckDB standard) - Returns TODO AST nodes - actual parsing done by DuckDB database
-
Catalog (
/internal/engine/duckdb/catalog.go)- Minimal catalog implementation
- Sets "main" as the default schema and "memory" as the default catalog
- Does not include pre-generated types/functions (database-backed only)
-
Analyzer (
/internal/engine/duckdb/analyzer/analyze.go)- REQUIRED for DuckDB engine (not optional like PostgreSQL)
- Connects to DuckDB database via
github.com/marcboeker/go-duckdb - Uses PREPARE and DESCRIBE to analyze queries
- Handles all SQL parsing and validation via the database
- Queries column metadata from prepared statements
- Normalizes DuckDB types to sqlc-compatible types
-
Reserved Keywords (
/internal/engine/duckdb/reserved.go)- DuckDB reserved keywords based on official documentation
- Includes LAMBDA (reserved as of DuckDB 1.3.0)
- Can be queried from DuckDB using
SELECT * FROM duckdb_keywords()
Added EngineDuckDB constant to /internal/config/config.go:
const (
EngineDuckDB Engine = "duckdb"
EngineMySQL Engine = "mysql"
EnginePostgreSQL Engine = "postgresql"
EngineSQLite Engine = "sqlite"
)Registered in /internal/compiler/engine.go with required database analyzer:
case config.EngineDuckDB:
c.parser = duckdb.NewParser()
c.catalog = duckdb.NewCatalog()
c.selector = newDefaultSelector()
// DuckDB requires database analyzer
if conf.Database == nil {
return nil, fmt.Errorf("duckdb engine requires database configuration")
}
if conf.Analyzer.Database == nil || *conf.Analyzer.Database {
c.analyzer = analyzer.Cached(
duckdbanalyze.New(c.client, *conf.Database),
combo.Global,
*conf.Database,
)
}version: "2"
sql:
- name: "basic"
engine: "duckdb"
schema: "schema/"
queries: "query/"
database:
uri: ":memory:" # or path to .db file
gen:
go:
out: "db"
package: "db"
emit_json_tags: true
emit_interface: trueCREATE TABLE authors (
id INTEGER PRIMARY KEY,
name VARCHAR NOT NULL,
bio TEXT
);-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :exec
INSERT INTO authors (name, bio)
VALUES ($1, $2);- PostgreSQL: Optional database analyzer, rich Go-based catalog with pg_catalog, full AST parsing
- DuckDB: Required database analyzer, minimal catalog, no AST parsing (database validates SQL)
- MySQL/SQLite: Go-based catalog with built-in functions, TiDB/ANTLR parser with full AST
- DuckDB: Database-backed only, no Go-based catalog, minimal parser (database parses SQL)
DuckDB is the only engine that doesn't parse SQL in Go. All SQL parsing, validation, and type checking happens directly in the DuckDB database. This ensures 100% compatibility with DuckDB's SQL syntax without needing to maintain a separate parser.
DuckDB types are normalized to sqlc-compatible types:
| DuckDB Type | sqlc Type |
|---|---|
| INTEGER, INT, INT4 | integer |
| BIGINT, INT8, LONG | bigint |
| SMALLINT, INT2, SHORT | smallint |
| TINYINT, INT1 | tinyint |
| DOUBLE, FLOAT8 | double |
| REAL, FLOAT4, FLOAT | real |
| VARCHAR, TEXT, STRING | varchar |
| BOOLEAN, BOOL | boolean |
| DATE | date |
| TIME | time |
| TIMESTAMP | timestamp |
| TIMESTAMPTZ | timestamptz |
| BLOB, BYTEA, BINARY | bytea |
| UUID | uuid |
| JSON | json |
| DECIMAL, NUMERIC | decimal |
Added to go.mod:
github.com/marcboeker/go-duckdb v1.8.5-
Install dependencies (requires network access):
go mod tidy
-
Build sqlc:
go build ./cmd/sqlc
-
Run code generation:
./sqlc generate
An example project is provided in /examples/duckdb/basic/ with:
- Schema definitions
- Sample queries
- sqlc.yaml configuration
To test:
cd examples/duckdb/basic
sqlc generateDuckDB engine requires a database connection. You must configure:
database:
uri: "path/to/database.db" # or ":memory:" for in-memoryWithout this configuration, the compiler will return an error:
duckdb engine requires database configuration
- Network dependency: Requires network access to download go-duckdb initially
- Parameter type inference: DuckDB doesn't provide parameter types without execution, so parameters are typed as "any" by the analyzer
- Database required: Unlike other engines, DuckDB cannot generate code without a database connection (no offline mode)
- Improve parameter type inference by analyzing query patterns
- Add support for DuckDB-specific types (STRUCT, LIST, UNION, MAP)
- Support DuckDB extensions and extension-specific functions
- Add DuckDB-specific selector for custom column handling
- Improve error messages with DuckDB-specific error codes
- Cache database connections for better performance
- Support managed databases via database manager
/internal/engine/duckdb/parse.go- Minimal pass-through parser/internal/engine/duckdb/catalog.go- Minimal catalog/internal/engine/duckdb/reserved.go- Reserved keywords/internal/engine/duckdb/analyzer/analyze.go- Database analyzer/examples/duckdb/basic/schema/schema.sql- Example schema/examples/duckdb/basic/query/query.sql- Example queries/examples/duckdb/basic/sqlc.yaml- Example configuration
/internal/config/config.go- AddedEngineDuckDBconstant/internal/compiler/engine.go- Registered DuckDB engine with analyzer/go.mod- Addedgithub.com/marcboeker/go-duckdb v1.8.5
- No SQL parsing in Go: DuckDB engine validates all SQL via the database, not in Go code
- DuckDB uses "main" as the default schema (different from PostgreSQL's "public")
- DuckDB uses "memory" as the default catalog name
- Comment syntax supports only
--and/* */, not# - Reserved keyword LAMBDA was added in DuckDB 1.3.0
- Reserved keyword GRANT was removed in DuckDB 1.3.0
- 100% compatibility with DuckDB syntax since the database itself parses SQL