Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add DuckDB engine support with database-backed catalog
This commit adds comprehensive DuckDB support to sqlc using a
database-backed approach with required analyzer, similar to PostgreSQL's
optional analyzer pattern but made mandatory for DuckDB.

Key features:
- Database-backed catalog using DuckDB connections
- Required analyzer for type inference and schema information
- TiDB parser for SQL parsing (shared with MySQL engine)
- DuckDB reserved keywords implementation
- Type normalization for DuckDB-specific types
- Example project demonstrating basic usage

Implementation details:
- Parser: Uses TiDB parser, supports -- and /* */ comments
- Catalog: Minimal implementation, no pre-generated types
- Analyzer: Required component, connects via go-duckdb driver
- Converter: Reuses Dolphin/MySQL AST converter
- Reserved keywords: Based on DuckDB 1.3.0 specification

Files created:
- internal/engine/duckdb/parse.go - Parser implementation
- internal/engine/duckdb/catalog.go - Minimal catalog
- internal/engine/duckdb/convert.go - AST converter
- internal/engine/duckdb/reserved.go - Reserved keywords
- internal/engine/duckdb/analyzer/analyze.go - Database analyzer
- examples/duckdb/basic/ - Example project
- DUCKDB_SUPPORT.md - Comprehensive documentation

Files modified:
- internal/config/config.go - Added EngineDuckDB constant
- internal/compiler/engine.go - Registered DuckDB with analyzer
- go.mod - Added github.com/marcboeker/go-duckdb v1.8.5

Requirements:
- Database connection is required (not optional)
- Configuration must include database.uri parameter
- Run 'go mod tidy' to download dependencies (network required)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
  • Loading branch information
claude committed Oct 21, 2025
commit c9a899b2a554cbe96ec662d1f179f0b3a8a2be23
241 changes: 241 additions & 0 deletions DUCKDB_SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# DuckDB Support for sqlc

This document describes the DuckDB engine implementation for sqlc.

## Overview

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.

## Implementation Details

### Core Components

1. **Parser** (`/internal/engine/duckdb/parse.go`)
- Uses the TiDB parser (same as MySQL/Dolphin engine)
- Implements the `Parser` interface with `Parse()`, `CommentSyntax()`, and `IsReservedKeyword()` methods
- Supports `--` and `/* */` comment styles (DuckDB standard)

2. **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)

3. **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
- Queries column metadata from prepared statements
- Normalizes DuckDB types to sqlc-compatible types

4. **AST Converter** (`/internal/engine/duckdb/convert.go`)
- Copied from Dolphin/MySQL implementation
- Converts TiDB parser AST to sqlc universal AST

5. **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()`

## Configuration

### Engine Registration

Added `EngineDuckDB` constant to `/internal/config/config.go`:
```go
const (
EngineDuckDB Engine = "duckdb"
EngineMySQL Engine = "mysql"
EnginePostgreSQL Engine = "postgresql"
EngineSQLite Engine = "sqlite"
)
```

### Compiler Integration

Registered in `/internal/compiler/engine.go` with required database analyzer:
```go
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,
)
}
```

## Usage Example

### sqlc.yaml Configuration

```yaml
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: true
```

### Schema Example

```sql
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
name VARCHAR NOT NULL,
bio TEXT
);
```

### Query Example

```sql
-- 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);
```

## Key Differences from Other Engines

### vs PostgreSQL
- **PostgreSQL**: Optional database analyzer, rich Go-based catalog with pg_catalog
- **DuckDB**: Required database analyzer, minimal catalog

### vs MySQL/SQLite
- **MySQL/SQLite**: Go-based catalog with built-in functions
- **DuckDB**: Database-backed only, no Go-based catalog

## Type Mapping

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 |

## Dependencies

Added to `go.mod`:
```go
github.com/marcboeker/go-duckdb v1.8.5
```

## Setup Instructions

1. **Install dependencies** (requires network access):
```bash
go mod tidy
```

2. **Build sqlc**:
```bash
go build ./cmd/sqlc
```

3. **Run code generation**:
```bash
./sqlc generate
```

## Testing

An example project is provided in `/examples/duckdb/basic/` with:
- Schema definitions
- Sample queries
- sqlc.yaml configuration

To test:
```bash
cd examples/duckdb/basic
sqlc generate
```

## Database Requirements

DuckDB engine **requires** a database connection. You must configure:
```yaml
database:
uri: "path/to/database.db" # or ":memory:" for in-memory
```

Without this configuration, the compiler will return an error:
```
duckdb engine requires database configuration
```

## Limitations

1. **Network dependency**: Requires network access to download go-duckdb initially
2. **Parameter type inference**: DuckDB doesn't provide parameter types without execution, so parameters are typed as "any" by the analyzer
3. **Parser limitations**: Uses TiDB parser which may not support all DuckDB-specific syntax (STRUCT, LIST, UNION types may require custom handling)

## Future Enhancements

1. Improve parameter type inference
2. Add support for DuckDB-specific types (STRUCT, LIST, UNION, MAP)
3. Support DuckDB extensions
4. Add DuckDB-specific selector for custom column handling
5. Improve error messages with DuckDB-specific error codes

## Files Modified/Created

### Created:
- `/internal/engine/duckdb/parse.go`
- `/internal/engine/duckdb/catalog.go`
- `/internal/engine/duckdb/convert.go`
- `/internal/engine/duckdb/reserved.go`
- `/internal/engine/duckdb/analyzer/analyze.go`
- `/examples/duckdb/basic/schema/schema.sql`
- `/examples/duckdb/basic/query/query.sql`
- `/examples/duckdb/basic/sqlc.yaml`

### Modified:
- `/internal/config/config.go` - Added `EngineDuckDB` constant
- `/internal/compiler/engine.go` - Registered DuckDB engine with analyzer
- `/go.mod` - Added `github.com/marcboeker/go-duckdb v1.8.5`

## Notes

- 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
23 changes: 23 additions & 0 deletions examples/duckdb/basic/query/query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- 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
);

-- name: UpdateAuthor :exec
UPDATE authors
SET name = $1, bio = $2
WHERE id = $3;

-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;
5 changes: 5 additions & 0 deletions examples/duckdb/basic/schema/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
name VARCHAR NOT NULL,
bio TEXT
);
14 changes: 14 additions & 0 deletions examples/duckdb/basic/sqlc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: "2"
sql:
- name: "basic"
engine: "duckdb"
schema: "schema/"
queries: "query/"
database:
uri: ":memory:"
gen:
go:
out: "db"
package: "db"
emit_json_tags: true
emit_interface: true
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/jackc/pgx/v5 v5.7.6
github.com/jinzhu/inflection v1.0.0
github.com/lib/pq v1.10.9
github.com/marcboeker/go-duckdb v1.8.5
github.com/pganalyze/pg_query_go/v6 v6.1.0
github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0
github.com/riza-io/grpc-go v0.2.0
Expand Down
17 changes: 17 additions & 0 deletions internal/compiler/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/dbmanager"
"github.com/sqlc-dev/sqlc/internal/engine/dolphin"
"github.com/sqlc-dev/sqlc/internal/engine/duckdb"
duckdbanalyze "github.com/sqlc-dev/sqlc/internal/engine/duckdb/analyzer"
"github.com/sqlc-dev/sqlc/internal/engine/postgresql"
pganalyze "github.com/sqlc-dev/sqlc/internal/engine/postgresql/analyzer"
"github.com/sqlc-dev/sqlc/internal/engine/sqlite"
Expand Down Expand Up @@ -37,6 +39,21 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err
}

switch conf.Engine {
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,
)
}
case config.EngineSQLite:
c.parser = sqlite.NewParser()
c.catalog = sqlite.NewCatalog()
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (p *Paths) UnmarshalYAML(unmarshal func(interface{}) error) error {
}

const (
EngineDuckDB Engine = "duckdb"
EngineMySQL Engine = "mysql"
EnginePostgreSQL Engine = "postgresql"
EngineSQLite Engine = "sqlite"
Expand Down
Loading