Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ When using sqlc with multiple database backends, each engine generates its own `
- `generated_querier.go` — a common `Querier` interface in the parent package
- `generated_models.go` — common domain model types (converted from engine-specific types)
- `generated_errors.go` — shared sentinel errors (`ErrNotFound`, `ErrMismatchedSlices`)
- `generated_wrapper_sqlite.go` — SQLite wrapper implementing the common `Querier`
- `generated_wrapper_postgres.go` — PostgreSQL wrapper implementing the common `Querier`
- `generated_wrapper_mysql.go` — MySQL/MariaDB wrapper implementing the common `Querier`
- `generated_wrapper_<engine>.go` — one wrapper per engine, implementing the common `Querier`

The wrappers handle engine differences automatically:

Expand All @@ -23,8 +21,8 @@ The wrappers handle engine differences automatically:
## Requirements

- Go 1.24+ (uses the `tool` directive in `go.mod`)
- sqlc with queries for all three engines: `query.sqlite.sql`, `query.postgres.sql`, `query.mysql.sql`
- sqlc output packages named `sqlitedb`, `postgresdb`, `mysqldb` (siblings of the target package)
- sqlc with queries for the engines you need (e.g. `query.sqlite.sql`, `query.postgres.sql`)
- sqlc output packages named to match the `--engine` flags you pass (siblings of the target package)

## Installation

Expand All @@ -44,12 +42,39 @@ require github.com/kalbasit/sqlc-multi-db vX.Y.Z

## Usage

```
sqlc-multi-db --engine name:package [--engine ...] /path/to/source/querier.go
```

The `--engine` flag is **repeatable** and takes the form `name:package`:

- `name` — engine identifier used in generated file names (e.g. `sqlite`, `postgres`, `mysql`)
- `package` — directory name of the sqlc-generated package for that engine (e.g. `sqlitedb`, `postgresdb`)

At least one `--engine` flag is required; the tool exits with an error if none are provided.

### Examples

SQLite + PostgreSQL only:

```bash
go tool github.com/kalbasit/sqlc-multi-db --engine sqlite:sqlitedb --engine postgres:postgresdb postgresdb/querier.go
```

All three engines:

```bash
go tool github.com/kalbasit/sqlc-multi-db --engine sqlite:sqlitedb --engine postgres:postgresdb --engine mysql:mysqldb postgresdb/querier.go
```

### go:generate

Add a `generate.go` file in your database package (e.g., `pkg/database/generate.go`):

```go
package database

//go:generate go tool github.com/kalbasit/sqlc-multi-db postgresdb/querier.go
//go:generate go tool github.com/kalbasit/sqlc-multi-db --engine sqlite:sqlitedb --engine postgres:postgresdb postgresdb/querier.go
```

Then run:
Expand All @@ -64,16 +89,14 @@ go generate ./pkg/database
pkg/database/
sqlitedb/ # sqlc-generated (sqlite engine)
postgresdb/ # sqlc-generated (postgres engine) ← source of truth
mysqldb/ # sqlc-generated (mysql engine)
database.go # your Open() factory
errors.go # your custom errors (IsDeadlockError, etc.)
generate.go # //go:generate directive
generated_errors.go # generated
generated_models.go # generated
generated_querier.go # generated
generated_errors.go # generated
generated_models.go # generated
generated_querier.go # generated
generated_wrapper_sqlite.go # generated
generated_wrapper_postgres.go # generated
generated_wrapper_mysql.go # generated
```

## Bulk Operations (`@bulk-for`)
Expand Down Expand Up @@ -122,7 +145,10 @@ The generator logic is also available as a library:
```go
import "github.com/kalbasit/sqlc-multi-db/generator"

generator.Run("/path/to/postgresdb/querier.go")
generator.Run("/path/to/postgresdb/querier.go", []generator.Engine{
{Name: "sqlite", Package: "sqlitedb"},
{Name: "postgres", Package: "postgresdb"},
})
```

## License
Expand Down
8 changes: 1 addition & 7 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ import (

// Run is the main entry point for the generator.
// querierPath is the path to the source querier.go file (e.g., postgresdb/querier.go).
func Run(querierPath string) {
engines := []Engine{
{Name: "sqlite", Package: "sqlitedb"},
{Name: "postgres", Package: "postgresdb"},
{Name: "mysql", Package: "mysqldb"},
}

func Run(querierPath string, engines []Engine) {
absQuerierPath, err := filepath.Abs(querierPath)
if err != nil {
log.Fatalf("resolving querier path: %v", err)
Expand Down
52 changes: 41 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,61 @@
package main

import (
"errors"
"flag"
"fmt"
"log"
"os"
"strings"

"github.com/kalbasit/sqlc-multi-db/generator"
)

var errInvalidEngineFormat = errors.New("invalid engine format: expected name:package")

type engineFlag []generator.Engine

func (e *engineFlag) String() string {
parts := make([]string, len(*e))
for i, eng := range *e {
parts[i] = eng.Name + ":" + eng.Package
}

return strings.Join(parts, ", ")
}

func (e *engineFlag) Set(value string) error {
parts := strings.SplitN(value, ":", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return fmt.Errorf("%w: got %q", errInvalidEngineFormat, value)
}

*e = append(*e, generator.Engine{Name: parts[0], Package: parts[1]})

return nil
}

func main() {
var querierPath string
// Handle cases where go run might pass "--"
for _, arg := range os.Args[1:] {
if arg != "--" && !strings.HasPrefix(arg, "-") {
querierPath = arg

break
}
var engines engineFlag

flag.Var(&engines, "engine", "Engine in name:package format (repeatable)")

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "USAGE: %s [--engine name:package ...] /path/to/source/querier.go\n", os.Args[0])
}

if querierPath == "" {
log.Fatalf("USAGE: %s /path/to/source/querier.go", os.Args[0])
flag.Parse()

if len(engines) == 0 || flag.NArg() != 1 {
flag.Usage()
os.Exit(1)
}

querierPath := flag.Arg(0)

if _, err := os.Stat(querierPath); err != nil {
log.Fatalf("stat(%q): %s", querierPath, err)
}

generator.Run(querierPath)
generator.Run(querierPath, []generator.Engine(engines))
}
Loading