|
| 1 | +# DuckDB Support for sqlc |
| 2 | + |
| 3 | +This document describes the DuckDB engine implementation for sqlc. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +## Implementation Details |
| 10 | + |
| 11 | +### Core Components |
| 12 | + |
| 13 | +1. **Parser** (`/internal/engine/duckdb/parse.go`) |
| 14 | + - Uses the TiDB parser (same as MySQL/Dolphin engine) |
| 15 | + - Implements the `Parser` interface with `Parse()`, `CommentSyntax()`, and `IsReservedKeyword()` methods |
| 16 | + - Supports `--` and `/* */` comment styles (DuckDB standard) |
| 17 | + |
| 18 | +2. **Catalog** (`/internal/engine/duckdb/catalog.go`) |
| 19 | + - Minimal catalog implementation |
| 20 | + - Sets "main" as the default schema and "memory" as the default catalog |
| 21 | + - Does not include pre-generated types/functions (database-backed only) |
| 22 | + |
| 23 | +3. **Analyzer** (`/internal/engine/duckdb/analyzer/analyze.go`) |
| 24 | + - **REQUIRED** for DuckDB engine (not optional like PostgreSQL) |
| 25 | + - Connects to DuckDB database via `github.com/marcboeker/go-duckdb` |
| 26 | + - Uses PREPARE and DESCRIBE to analyze queries |
| 27 | + - Queries column metadata from prepared statements |
| 28 | + - Normalizes DuckDB types to sqlc-compatible types |
| 29 | + |
| 30 | +4. **AST Converter** (`/internal/engine/duckdb/convert.go`) |
| 31 | + - Copied from Dolphin/MySQL implementation |
| 32 | + - Converts TiDB parser AST to sqlc universal AST |
| 33 | + |
| 34 | +5. **Reserved Keywords** (`/internal/engine/duckdb/reserved.go`) |
| 35 | + - DuckDB reserved keywords based on official documentation |
| 36 | + - Includes LAMBDA (reserved as of DuckDB 1.3.0) |
| 37 | + - Can be queried from DuckDB using `SELECT * FROM duckdb_keywords()` |
| 38 | + |
| 39 | +## Configuration |
| 40 | + |
| 41 | +### Engine Registration |
| 42 | + |
| 43 | +Added `EngineDuckDB` constant to `/internal/config/config.go`: |
| 44 | +```go |
| 45 | +const ( |
| 46 | + EngineDuckDB Engine = "duckdb" |
| 47 | + EngineMySQL Engine = "mysql" |
| 48 | + EnginePostgreSQL Engine = "postgresql" |
| 49 | + EngineSQLite Engine = "sqlite" |
| 50 | +) |
| 51 | +``` |
| 52 | + |
| 53 | +### Compiler Integration |
| 54 | + |
| 55 | +Registered in `/internal/compiler/engine.go` with required database analyzer: |
| 56 | +```go |
| 57 | +case config.EngineDuckDB: |
| 58 | + c.parser = duckdb.NewParser() |
| 59 | + c.catalog = duckdb.NewCatalog() |
| 60 | + c.selector = newDefaultSelector() |
| 61 | + // DuckDB requires database analyzer |
| 62 | + if conf.Database == nil { |
| 63 | + return nil, fmt.Errorf("duckdb engine requires database configuration") |
| 64 | + } |
| 65 | + if conf.Analyzer.Database == nil || *conf.Analyzer.Database { |
| 66 | + c.analyzer = analyzer.Cached( |
| 67 | + duckdbanalyze.New(c.client, *conf.Database), |
| 68 | + combo.Global, |
| 69 | + *conf.Database, |
| 70 | + ) |
| 71 | + } |
| 72 | +``` |
| 73 | + |
| 74 | +## Usage Example |
| 75 | + |
| 76 | +### sqlc.yaml Configuration |
| 77 | + |
| 78 | +```yaml |
| 79 | +version: "2" |
| 80 | +sql: |
| 81 | + - name: "basic" |
| 82 | + engine: "duckdb" |
| 83 | + schema: "schema/" |
| 84 | + queries: "query/" |
| 85 | + database: |
| 86 | + uri: ":memory:" # or path to .db file |
| 87 | + gen: |
| 88 | + go: |
| 89 | + out: "db" |
| 90 | + package: "db" |
| 91 | + emit_json_tags: true |
| 92 | + emit_interface: true |
| 93 | +``` |
| 94 | +
|
| 95 | +### Schema Example |
| 96 | +
|
| 97 | +```sql |
| 98 | +CREATE TABLE authors ( |
| 99 | + id INTEGER PRIMARY KEY, |
| 100 | + name VARCHAR NOT NULL, |
| 101 | + bio TEXT |
| 102 | +); |
| 103 | +``` |
| 104 | + |
| 105 | +### Query Example |
| 106 | + |
| 107 | +```sql |
| 108 | +-- name: GetAuthor :one |
| 109 | +SELECT * FROM authors |
| 110 | +WHERE id = $1 LIMIT 1; |
| 111 | + |
| 112 | +-- name: ListAuthors :many |
| 113 | +SELECT * FROM authors |
| 114 | +ORDER BY name; |
| 115 | + |
| 116 | +-- name: CreateAuthor :exec |
| 117 | +INSERT INTO authors (name, bio) |
| 118 | +VALUES ($1, $2); |
| 119 | +``` |
| 120 | + |
| 121 | +## Key Differences from Other Engines |
| 122 | + |
| 123 | +### vs PostgreSQL |
| 124 | +- **PostgreSQL**: Optional database analyzer, rich Go-based catalog with pg_catalog |
| 125 | +- **DuckDB**: Required database analyzer, minimal catalog |
| 126 | + |
| 127 | +### vs MySQL/SQLite |
| 128 | +- **MySQL/SQLite**: Go-based catalog with built-in functions |
| 129 | +- **DuckDB**: Database-backed only, no Go-based catalog |
| 130 | + |
| 131 | +## Type Mapping |
| 132 | + |
| 133 | +DuckDB types are normalized to sqlc-compatible types: |
| 134 | + |
| 135 | +| DuckDB Type | sqlc Type | |
| 136 | +|-------------|-----------| |
| 137 | +| INTEGER, INT, INT4 | integer | |
| 138 | +| BIGINT, INT8, LONG | bigint | |
| 139 | +| SMALLINT, INT2, SHORT | smallint | |
| 140 | +| TINYINT, INT1 | tinyint | |
| 141 | +| DOUBLE, FLOAT8 | double | |
| 142 | +| REAL, FLOAT4, FLOAT | real | |
| 143 | +| VARCHAR, TEXT, STRING | varchar | |
| 144 | +| BOOLEAN, BOOL | boolean | |
| 145 | +| DATE | date | |
| 146 | +| TIME | time | |
| 147 | +| TIMESTAMP | timestamp | |
| 148 | +| TIMESTAMPTZ | timestamptz | |
| 149 | +| BLOB, BYTEA, BINARY | bytea | |
| 150 | +| UUID | uuid | |
| 151 | +| JSON | json | |
| 152 | +| DECIMAL, NUMERIC | decimal | |
| 153 | + |
| 154 | +## Dependencies |
| 155 | + |
| 156 | +Added to `go.mod`: |
| 157 | +```go |
| 158 | +github.com/marcboeker/go-duckdb v1.8.5 |
| 159 | +``` |
| 160 | + |
| 161 | +## Setup Instructions |
| 162 | + |
| 163 | +1. **Install dependencies** (requires network access): |
| 164 | + ```bash |
| 165 | + go mod tidy |
| 166 | + ``` |
| 167 | + |
| 168 | +2. **Build sqlc**: |
| 169 | + ```bash |
| 170 | + go build ./cmd/sqlc |
| 171 | + ``` |
| 172 | + |
| 173 | +3. **Run code generation**: |
| 174 | + ```bash |
| 175 | + ./sqlc generate |
| 176 | + ``` |
| 177 | + |
| 178 | +## Testing |
| 179 | + |
| 180 | +An example project is provided in `/examples/duckdb/basic/` with: |
| 181 | +- Schema definitions |
| 182 | +- Sample queries |
| 183 | +- sqlc.yaml configuration |
| 184 | + |
| 185 | +To test: |
| 186 | +```bash |
| 187 | +cd examples/duckdb/basic |
| 188 | +sqlc generate |
| 189 | +``` |
| 190 | + |
| 191 | +## Database Requirements |
| 192 | + |
| 193 | +DuckDB engine **requires** a database connection. You must configure: |
| 194 | +```yaml |
| 195 | +database: |
| 196 | + uri: "path/to/database.db" # or ":memory:" for in-memory |
| 197 | +``` |
| 198 | +
|
| 199 | +Without this configuration, the compiler will return an error: |
| 200 | +``` |
| 201 | +duckdb engine requires database configuration |
| 202 | +``` |
| 203 | + |
| 204 | +## Limitations |
| 205 | + |
| 206 | +1. **Network dependency**: Requires network access to download go-duckdb initially |
| 207 | +2. **Parameter type inference**: DuckDB doesn't provide parameter types without execution, so parameters are typed as "any" by the analyzer |
| 208 | +3. **Parser limitations**: Uses TiDB parser which may not support all DuckDB-specific syntax (STRUCT, LIST, UNION types may require custom handling) |
| 209 | + |
| 210 | +## Future Enhancements |
| 211 | + |
| 212 | +1. Improve parameter type inference |
| 213 | +2. Add support for DuckDB-specific types (STRUCT, LIST, UNION, MAP) |
| 214 | +3. Support DuckDB extensions |
| 215 | +4. Add DuckDB-specific selector for custom column handling |
| 216 | +5. Improve error messages with DuckDB-specific error codes |
| 217 | + |
| 218 | +## Files Modified/Created |
| 219 | + |
| 220 | +### Created: |
| 221 | +- `/internal/engine/duckdb/parse.go` |
| 222 | +- `/internal/engine/duckdb/catalog.go` |
| 223 | +- `/internal/engine/duckdb/convert.go` |
| 224 | +- `/internal/engine/duckdb/reserved.go` |
| 225 | +- `/internal/engine/duckdb/analyzer/analyze.go` |
| 226 | +- `/examples/duckdb/basic/schema/schema.sql` |
| 227 | +- `/examples/duckdb/basic/query/query.sql` |
| 228 | +- `/examples/duckdb/basic/sqlc.yaml` |
| 229 | + |
| 230 | +### Modified: |
| 231 | +- `/internal/config/config.go` - Added `EngineDuckDB` constant |
| 232 | +- `/internal/compiler/engine.go` - Registered DuckDB engine with analyzer |
| 233 | +- `/go.mod` - Added `github.com/marcboeker/go-duckdb v1.8.5` |
| 234 | + |
| 235 | +## Notes |
| 236 | + |
| 237 | +- DuckDB uses "main" as the default schema (different from PostgreSQL's "public") |
| 238 | +- DuckDB uses "memory" as the default catalog name |
| 239 | +- Comment syntax supports only `--` and `/* */`, not `#` |
| 240 | +- Reserved keyword LAMBDA was added in DuckDB 1.3.0 |
| 241 | +- Reserved keyword GRANT was removed in DuckDB 1.3.0 |
0 commit comments