On this page

Generate SQL wrappers from Go

Use the root package, seagull.sh/seagull, when your build tool needs to choose the input file system and decide when to publish generated files. This guide assumes a project with annotated queries in db/queries and schema migrations in db/migrations.

Create a generator command

Save this as cmd/generate/main.go in a Go module that depends on Seagull. It targets SQLite; select the dialect that matches your SQL when adapting it.

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"

	"seagull.sh/seagull"
	"seagull.sh/seagull/sdk/dialect/dialect_engine_sqlite"
)

func main() {
	project := flag.String("project", ".", "project directory")
	output := flag.String("out", "internal/store", "generated output directory")
	flag.Parse()
	if err := generate(context.Background(), *project, *output); err != nil {
		log.Fatal(err)
	}
}

func generate(ctx context.Context, directory, output string) error {
	generator, err := seagull.New(dialect_engine_sqlite.New())
	if err != nil {
		return err
	}
	result, err := generator.Generate(ctx, seagull.Input{
		Migrations: os.DirFS(directory), Package: "store",
		MigrationDir: "db/migrations", QueryDir: "db/queries",
	})
	if err != nil {
		return fmt.Errorf("generating: %w", err)
	}
	for _, diagnostic := range result.Diagnostics {
		fmt.Fprintf(os.Stderr, "%s:%d:%d: [%s] %s\n", diagnostic.Filename,
			diagnostic.Line, diagnostic.Column, diagnostic.Code, diagnostic.Message)
	}
	if err := result.Err(); err != nil {
		return err
	}
	if err := seagull.WriteFiles(output, result); err != nil {
		return err
	}
	fmt.Printf("generated %d queries\n", result.QueryCount)
	return nil
}

Generate reads the SQL without opening a database. An operational error stops the run; SQL problems arrive in result.Diagnostics. The command prints those diagnostics and checks result.Err() before writing. A run reports at most 10,000 diagnostics and summarises the rest in one Q066 at the highest severity among them, so result.Err() still fails when an error was left out; seagull.WithMaxDiagnostics changes the cap. A panic in the dialect or the emitter is recovered too: it comes back as a Q070 diagnostic or an error matching seagull.ErrPanic, and its stack is logged once at warning level.

Run and review the output

From your project root:

go run ./cmd/generate -project . -out internal/store
go test ./...

Review the generated API and compile its callers. The library's default database/sql emitter writes prepared.go; pass seagull.WithEmitter(emit_driver_databasesql.New(emit_driver_databasesql.WithPrepared(false))) if you need the CLI's default instead.

Use a dedicated output directory. WriteFiles replaces existing files with the same names and prunes stale files bearing its generated header. A hand-written file with a colliding name can be overwritten.

Add a check mode

For a CI check, compare result.Files with files in the output directory before calling WriteFiles, and return a failing status when they differ. The complete example implements this comparison. If you already use sgl.yaml, prefer sgl generate --check or assemble the generator from configuration.

See the generator API for options and output writing for partial results and pruning.

Examples for this page