Generating from Go
On this page
On SQLite:
- dialects.New picks the SQLite dialect by name, as a tool reading its own configuration would
- the default emitter is database/sql, which also writes prepared.go; sgl.yaml sets go.prepared: true so sgl generate agrees
- the check compares each generated file and looks for files with the generated header that this run would prune
sgl is a thin command over a Go package, seagull.sh/seagull, and anything that can import a package can generate code the way sgl generate does. A build tool, a framework or a project's own generator can hand Seagull a dialect and the SQL, get back the generated files and every diagnostic, and decide for itself what to print, what to fail on and where to write. This example is such a program: it generates a store package from Go, and it has a check mode for CI.
The project
A notes table, with an INSERT and a list. sgl.yaml describes the same project for sgl, so you can compare the two: the program does in Go what this file asks of sgl generate.
sgl.yaml
# Copyright 2026 PolitePixels Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This project stands against fascism, authoritarianism, and all forms of
# oppression. We built this to empower people, not to enable those who would
# strip others of their rights and dignity.
version: 1
databases:
- name: notes
dialect: sqlite
migrations: db/migrations
queries: db/queries
go:
package: store
out: internal/store
prepared: true
types:
overrides:
- column: notes.settings
go: encoding/json.RawMessage
The type override says that notes.settings, a TEXT column holding JSON, should come back as a json.RawMessage. prepared: true matters too, as the end of this page explains.
The program
main.go only parses a -check flag and exits 1 when the package is stale. The work is in run:
example.go
package main
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"seagull.sh/seagull"
"seagull.sh/seagull/sdk/dialect/dialect_registry"
)
// run generates the store package from the project's migrations and queries, as a build
// tool that embeds Seagull does. It writes the package into directory or, when check is
// set, writes nothing and reports whether directory is out of date.
//
// Takes project (fs.FS) which holds db/migrations and db/queries.
// Takes directory (string) which is the package directory, such as internal/store.
// Takes check (bool) which compares the generated files with directory instead of writing
// them.
// Takes output (io.Writer) which receives the diagnostics and a summary.
//
// Returns bool which is true when check found directory out of date.
// Returns error when generation fails or a query has an error diagnostic.
func run(ctx context.Context, project fs.FS, directory string, check bool, output io.Writer) (bool, error) {
sqlite, err := dialect_registry.New("sqlite", dialect_registry.Options{})
if err != nil {
return false, err
}
generator, err := seagull.New(sqlite, seagull.WithTypeOverrides(seagull.TypeOverride{
SQLTypeName: "",
Column: "notes.settings",
GoPackage: "encoding/json",
GoName: "RawMessage",
}))
if err != nil {
return false, fmt.Errorf("configuring the generator: %w", err)
}
result, err := generator.Generate(ctx, seagull.Input{
Migrations: project,
Queries: nil,
Package: "store",
MigrationDir: "db/migrations",
QueryDir: "db/queries",
})
if err != nil {
return false, err
}
for _, diagnostic := range result.Diagnostics {
fmt.Fprintf(output, "%s:%d:%d: %s[%s]: %s\n", diagnostic.Filename, diagnostic.Line, diagnostic.Column,
diagnostic.Severity, diagnostic.Code, diagnostic.Message)
}
if err := result.Err(); err != nil {
return false, err
}
if check {
stale, err := outOfDate(result, directory)
if err != nil {
return false, err
}
if len(stale) == 0 {
fmt.Fprintln(output, "up to date")
return false, nil
}
fmt.Fprintf(output, "out of date: %s\n", strings.Join(stale, ", "))
return true, nil
}
if err := seagull.WriteFiles(directory, result); err != nil {
return false, err
}
fmt.Fprintf(output, "wrote %d files for %d queries\n", len(result.Files), result.QueryCount)
return false, nil
}
// outOfDate lists the files WriteFiles would change: a generated file that is missing or
// differs, and a file an earlier run generated that this run no longer does, such as the
// file of a deleted query, which WriteFiles would remove.
//
// Takes result (*seagull.Result) which is the generation to compare.
// Takes directory (string) which holds the package as it is now.
//
// Returns []string which holds the names of the files that would change, sorted.
// Returns error when the directory cannot be read.
func outOfDate(result *seagull.Result, directory string) ([]string, error) {
var stale []string
generated := make(map[string]bool, len(result.Files))
for _, file := range result.Files {
generated[file.Name] = true
existing, err := os.ReadFile(filepath.Join(directory, file.Name)) //nolint:gosec // a file of the package directory
if err != nil || !bytes.Equal(existing, file.Content) {
stale = append(stale, file.Name)
}
}
entries, err := os.ReadDir(directory)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
for _, entry := range entries {
if generated[entry.Name()] || entry.IsDir() {
continue
}
if firstLine(filepath.Join(directory, entry.Name())) == result.Header {
stale = append(stale, entry.Name())
}
}
slices.Sort(stale)
return stale, nil
}
// firstLine reads the first line of a file, where a generated file carries its header.
//
// Takes path (string) which is the file.
//
// Returns string which is the first line, or empty when the file cannot be read.
func firstLine(path string) string {
file, err := os.Open(path) //nolint:gosec // a file in the package directory
if err != nil {
return ""
}
defer func() { _ = file.Close() }()
scanner := bufio.NewScanner(file)
scanner.Scan()
return scanner.Text()
}
Choosing the dialect. dialect_registry.New("sqlite", dialect_registry.Options{}) builds a dialect from its name, which is how a tool that reads its own configuration picks one. A program that only ever targets SQLite could import seagull.sh/seagull/sdk/dialect/dialect_engine_sqlite and call dialect_engine_sqlite.New() instead. An unknown name is an error that lists the names it does know.
Building the generator. seagull.New takes the dialect as its first argument, so forgetting it is a compile error, and every other setting as an option with a default. WithTypeOverrides is the Go form of the override in sgl.yaml: the column, and the package and name of the Go type.
Generating. Generate reads the SQL through seagull.Input. Migrations is any fs.FS, here the working directory, so an embed.FS or an in-memory fstest.MapFS work as well. Queries is nil, which means "the same file system as the migrations", and the two directory fields are paths inside it.
Errors and diagnostics. The two are kept apart. Generate returns an error only when the run cannot go on: a file it cannot read, a limit exceeded, a cancelled context. Problems in your SQL come back as result.Diagnostics, each with a file, position, severity, code and message, and the program prints each one in the shape of the first line sgl prints for a diagnostic. result.Err() is non-nil when any of them is an error, and then the result holds no files.
Writing. seagull.WriteFiles writes the files into the directory, creating it if needed and replacing each file atomically. It also prunes: a file whose first line is the generated header, result.Header, but which this run did not produce, such as the file of a deleted query, is removed. Files without the header are never touched, so hand-written code can live beside the generated package.
Checking. In check mode the program writes nothing. outOfDate compares each generated file with the one on disk, and looks for files that carry the header but that WriteFiles would prune. Any difference means the committed package is stale.
What it generates
The override shows in the row struct: Settings is a *json.RawMessage, a pointer because the column can be NULL. A column override changes result rows only, so AddNote still takes the setting as a *string.
internal/store
type ListNotesRow struct {
ID int64 `json:"id"`
Body string `json:"body"`
Settings *json.RawMessage `json:"settings"`
}
func (queries *Queries) ListNotes(ctx context.Context) ([]ListNotesRow, error) {
rows, err := queries.reader.QueryContext(ctx, listNotesSQL)
if err != nil {
return nil, err
}
defer rows.Close()
var results []ListNotesRow
for rows.Next() {
var row ListNotesRow
if err := rows.Scan(&row.ID, &row.Body, sglScanOverride(&row.Settings)); err != nil {
return nil, err
}
results = append(results, row)
}
if err := rows.Err(); err != nil {
return nil, err
}
return results, nil
}
What it prints
The example's test runs the check against an empty directory, then writes, then checks again:
out of date: driver_composites.go, driver_values.go, models.go, notes.sql.go, prepared.go, querier.go, value_reading.go
wrote 7 files for 2 queries
up to date
A second test runs the check against the committed internal/store, which is how a project using this program would guard its generated code in CI, as sgl generate --check does in Seagull in CI.
The library and sgl agree
The committed package is the same whether go run . or sgl generate wrote it, and one setting makes that so. The library's database/sql emitter writes prepared.go unless told otherwise, while sgl's go.prepared setting defaults to false. This sgl.yaml sets prepared: true, so both write the file. A program that wants the other default passes seagull.WithEmitter(emit_driver_databasesql.New(emit_driver_databasesql.WithPrepared(false))).
Embedding Seagull covers every option of New and WriteFiles, and the package documentation has the full API.
Run it locally
From the directory you cloned the Seagull repository into:
cd seagull/examples/embedding && go run ./046_library_generate
Its test runs it too, and checks what it prints against output.txt:
cd seagull/examples/embedding && go test ./046_library_generate
The files
Every file of the example, as it is in the Seagull repository.
sgl.yaml
# Copyright 2026 PolitePixels Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This project stands against fascism, authoritarianism, and all forms of
# oppression. We built this to empower people, not to enable those who would
# strip others of their rights and dignity.
version: 1
databases:
- name: notes
dialect: sqlite
migrations: db/migrations
queries: db/queries
go:
package: store
out: internal/store
prepared: true
types:
overrides:
- column: notes.settings
go: encoding/json.RawMessage
db/migrations/0001_init.up.sql
CREATE TABLE notes (
id INTEGER PRIMARY KEY,
body TEXT NOT NULL,
settings TEXT
);
db/migrations/0001_init.down.sql
DROP TABLE notes;
db/queries/notes.sql
-- sgl.query(AddNote, exec)
INSERT INTO notes (body, settings) VALUES (?, ?);
-- sgl.query(ListNotes, many)
SELECT id, body, settings FROM notes ORDER BY id;
example.go
package main
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"seagull.sh/seagull"
"seagull.sh/seagull/sdk/dialect/dialect_registry"
)
// run generates the store package from the project's migrations and queries, as a build
// tool that embeds Seagull does. It writes the package into directory or, when check is
// set, writes nothing and reports whether directory is out of date.
//
// Takes project (fs.FS) which holds db/migrations and db/queries.
// Takes directory (string) which is the package directory, such as internal/store.
// Takes check (bool) which compares the generated files with directory instead of writing
// them.
// Takes output (io.Writer) which receives the diagnostics and a summary.
//
// Returns bool which is true when check found directory out of date.
// Returns error when generation fails or a query has an error diagnostic.
func run(ctx context.Context, project fs.FS, directory string, check bool, output io.Writer) (bool, error) {
sqlite, err := dialect_registry.New("sqlite", dialect_registry.Options{})
if err != nil {
return false, err
}
generator, err := seagull.New(sqlite, seagull.WithTypeOverrides(seagull.TypeOverride{
SQLTypeName: "",
Column: "notes.settings",
GoPackage: "encoding/json",
GoName: "RawMessage",
}))
if err != nil {
return false, fmt.Errorf("configuring the generator: %w", err)
}
result, err := generator.Generate(ctx, seagull.Input{
Migrations: project,
Queries: nil,
Package: "store",
MigrationDir: "db/migrations",
QueryDir: "db/queries",
})
if err != nil {
return false, err
}
for _, diagnostic := range result.Diagnostics {
fmt.Fprintf(output, "%s:%d:%d: %s[%s]: %s\n", diagnostic.Filename, diagnostic.Line, diagnostic.Column,
diagnostic.Severity, diagnostic.Code, diagnostic.Message)
}
if err := result.Err(); err != nil {
return false, err
}
if check {
stale, err := outOfDate(result, directory)
if err != nil {
return false, err
}
if len(stale) == 0 {
fmt.Fprintln(output, "up to date")
return false, nil
}
fmt.Fprintf(output, "out of date: %s\n", strings.Join(stale, ", "))
return true, nil
}
if err := seagull.WriteFiles(directory, result); err != nil {
return false, err
}
fmt.Fprintf(output, "wrote %d files for %d queries\n", len(result.Files), result.QueryCount)
return false, nil
}
// outOfDate lists the files WriteFiles would change: a generated file that is missing or
// differs, and a file an earlier run generated that this run no longer does, such as the
// file of a deleted query, which WriteFiles would remove.
//
// Takes result (*seagull.Result) which is the generation to compare.
// Takes directory (string) which holds the package as it is now.
//
// Returns []string which holds the names of the files that would change, sorted.
// Returns error when the directory cannot be read.
func outOfDate(result *seagull.Result, directory string) ([]string, error) {
var stale []string
generated := make(map[string]bool, len(result.Files))
for _, file := range result.Files {
generated[file.Name] = true
existing, err := os.ReadFile(filepath.Join(directory, file.Name)) //nolint:gosec // a file of the package directory
if err != nil || !bytes.Equal(existing, file.Content) {
stale = append(stale, file.Name)
}
}
entries, err := os.ReadDir(directory)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
for _, entry := range entries {
if generated[entry.Name()] || entry.IsDir() {
continue
}
if firstLine(filepath.Join(directory, entry.Name())) == result.Header {
stale = append(stale, entry.Name())
}
}
slices.Sort(stale)
return stale, nil
}
// firstLine reads the first line of a file, where a generated file carries its header.
//
// Takes path (string) which is the file.
//
// Returns string which is the first line, or empty when the file cannot be read.
func firstLine(path string) string {
file, err := os.Open(path) //nolint:gosec // a file in the package directory
if err != nil {
return ""
}
defer func() { _ = file.Close() }()
scanner := bufio.NewScanner(file)
scanner.Scan()
return scanner.Text()
}
main.go
// Command 046_library_generate generates this example's store package from Go, without
// sgl: it is what a build tool or framework does when it embeds Seagull.
//
// Run it from this directory with `go run .` to write internal/store, or `go run .
// -check` to write nothing and exit 1 when internal/store is out of date.
package main
import (
"context"
"flag"
"log"
"os"
)
// outputDirectory is where the generated package is written.
const outputDirectory = "internal/store"
func main() {
check := flag.Bool("check", false, "exit 1 when internal/store is out of date; write nothing")
flag.Parse()
stale, err := run(context.Background(), os.DirFS("."), outputDirectory, *check, os.Stdout)
if err != nil {
log.Fatal(err)
}
if stale {
os.Exit(1)
}
}
internal/store/driver_composites.go
// Code generated by sgl. DO NOT EDIT.
package store
import (
"database/sql"
"database/sql/driver"
"encoding"
"encoding/json"
"fmt"
"reflect"
"strings"
)
func sglScanOverride[T any](dest *T) sql.Scanner {
return sglOverrideScanner[T]{dest: dest}
}
type sglOverrideScanner[T any] struct {
dest *T
}
func (scanner sglOverrideScanner[T]) Scan(source any) error {
target := reflect.ValueOf(scanner.dest).Elem()
if source == nil {
return sglReadDriverNull(target)
}
var read sql.Null[T]
err := read.Scan(source)
if err == nil {
*scanner.dest = read.V
return nil
}
if sglReadOverride(target, source) != nil {
return err
}
return nil
}
func sglReadOverride(target reflect.Value, source any) error {
if target.Kind() == reflect.Pointer {
element := reflect.New(target.Type().Elem())
if err := sglReadOverride(element.Elem(), source); err != nil {
return err
}
target.Set(element)
return nil
}
if read, err := sglReadOverrideText(target, source); read {
return err
}
if target.Kind() == reflect.Bool {
converted, err := driver.Bool.ConvertValue(source)
if err != nil {
return err
}
isTrue, isBool := converted.(bool)
target.SetBool(isBool && isTrue)
return nil
}
return sglReadDriverValue(target, source)
}
func sglReadOverrideText(target reflect.Value, source any) (bool, error) {
var text []byte
switch typed := source.(type) {
case encoding.TextMarshaler:
marshalled, err := typed.MarshalText()
if err != nil {
return false, nil
}
text = marshalled
case string:
text = []byte(typed)
case []byte:
text = typed
default:
return false, nil
}
if unmarshaler, unmarshals := reflect.TypeAssert[encoding.TextUnmarshaler](target.Addr()); unmarshals {
return true, unmarshaler.UnmarshalText(text)
}
switch {
case target.Kind() == reflect.String:
target.SetString(string(text))
return true, nil
case target.Kind() == reflect.Slice && target.Type().Elem().Kind() == reflect.Uint8:
target.SetBytes(append([]byte(nil), text...))
return true, nil
default:
return false, nil
}
}
func sglReadDriverValue(target reflect.Value, source any) error {
if source == nil {
return sglReadDriverNull(target)
}
value := reflect.ValueOf(source)
if value.Type().AssignableTo(target.Type()) {
target.Set(value)
return nil
}
if target.Kind() == reflect.Pointer {
element := reflect.New(target.Type().Elem())
if err := sglReadDriverValue(element.Elem(), source); err != nil {
return err
}
target.Set(element)
return nil
}
if read, err := sglReadDriverLeaf(target, source); read {
return err
}
return sglReadDriverShape(target, value)
}
func sglReadDriverNull(target reflect.Value) error {
switch target.Kind() {
case reflect.Pointer, reflect.Slice, reflect.Map, reflect.Interface:
target.SetZero()
return nil
}
if scanner, scans := reflect.TypeAssert[sql.Scanner](target.Addr()); scans {
return scanner.Scan(nil)
}
return fmt.Errorf("converting NULL to %s is unsupported", target.Type())
}
func sglReadDriverShape(target reflect.Value, value reflect.Value) error {
switch target.Kind() {
case reflect.Slice:
return sglReadList(target, value, sglReadDriverValue)
case reflect.Map:
return sglReadMap(target, value, sglReadDriverValue)
case reflect.Struct:
return sglReadDriverStruct(target, value)
default:
return sglReadScalar(target, value)
}
}
func sglReadDriverLeaf(target reflect.Value, source any) (bool, error) {
address := target.Addr().Interface()
if unmarshaler, ok := address.(json.Unmarshaler); ok {
encoded, err := json.Marshal(source)
if err != nil {
return true, err
}
return true, unmarshaler.UnmarshalJSON(encoded)
}
if scanner, ok := address.(sql.Scanner); ok {
return true, scanner.Scan(source)
}
return false, nil
}
func sglReadDriverStruct(target reflect.Value, value reflect.Value) error {
source := value.Interface()
fields, ok := source.(map[string]any)
if !ok {
return sglUnreadable(value.Type(), target.Type())
}
target.SetZero()
structType := target.Type()
for index := range structType.NumField() {
name, readable := sglDriverFieldName(structType.Field(index))
if !readable {
continue
}
element, found := sglDriverField(fields, name)
if !found {
continue
}
if err := sglReadDriverValue(target.Field(index), element); err != nil {
return fmt.Errorf("field %s: %w", name, err)
}
}
return nil
}
func sglDriverFieldName(field reflect.StructField) (string, bool) {
if !field.IsExported() {
return "", false
}
name, _, _ := strings.Cut(field.Tag.Get("json"), ",")
switch name {
case "-":
return "", false
case "":
return field.Name, true
default:
return name, true
}
}
func sglDriverField(fields map[string]any, name string) (any, bool) {
if element, found := fields[name]; found {
return element, true
}
for key, element := range fields {
if strings.EqualFold(key, name) {
return element, true
}
}
return nil, false
}
internal/store/driver_values.go
// Code generated by sgl. DO NOT EDIT.
package store
import "database/sql/driver"
type sglDriverText struct {
value any
}
func (wrapped sglDriverText) Value() (driver.Value, error) {
converted, err := driver.DefaultParameterConverter.ConvertValue(wrapped.value)
if text, isBytes := converted.([]byte); isBytes && err == nil {
return string(text), nil
}
return converted, err
}
internal/store/models.go
// Code generated by sgl. DO NOT EDIT.
package store
import "encoding/json"
type Notes struct {
ID int64 `json:"id"`
Body string `json:"body"`
Settings *json.RawMessage `json:"settings"`
}
internal/store/notes.sql.go
// Code generated by sgl. DO NOT EDIT.
package store
import (
"context"
"encoding/json"
)
const addNoteSQL = `INSERT INTO notes (body, settings) VALUES (?1, ?2);`
type AddNoteParams struct {
Body string
Settings *json.RawMessage
}
func (queries *Queries) AddNote(ctx context.Context, params AddNoteParams) error {
_, err := queries.writer.ExecContext(ctx, addNoteSQL, params.Body, sglDriverText{value: params.Settings})
return err
}
const listNotesSQL = `SELECT id, body, settings FROM notes ORDER BY id;`
type ListNotesRow struct {
ID int64 `json:"id"`
Body string `json:"body"`
Settings *json.RawMessage `json:"settings"`
}
func (queries *Queries) ListNotes(ctx context.Context) ([]ListNotesRow, error) {
rows, err := queries.reader.QueryContext(ctx, listNotesSQL)
if err != nil {
return nil, err
}
defer rows.Close()
var results []ListNotesRow
for rows.Next() {
var row ListNotesRow
if err := rows.Scan(&row.ID, &row.Body, sglScanOverride(&row.Settings)); err != nil {
return nil, err
}
results = append(results, row)
}
if err := rows.Err(); err != nil {
return nil, err
}
return results, nil
}
internal/store/prepared.go
// Code generated by sgl. DO NOT EDIT.
package store
import (
"context"
"database/sql"
"errors"
"fmt"
"sync"
)
type PreparedDBTX struct {
db *sql.DB
stmts map[string]*sql.Stmt
mu sync.RWMutex
}
func Prepare(ctx context.Context, db *sql.DB) (*PreparedDBTX, error) {
staticQueries := []string{addNoteSQL, listNotesSQL}
stmts := make(map[string]*sql.Stmt, len(staticQueries))
for _, query := range staticQueries {
statement, err := db.PrepareContext(ctx, query)
if err != nil {
return nil, errors.Join(fmt.Errorf("preparing statement: %w", err), sglCloseStatements(stmts))
}
stmts[query] = statement
}
return &PreparedDBTX{db: db, stmts: stmts, mu: sync.RWMutex{}}, nil
}
func (prepared *PreparedDBTX) cachedStatement(query string) *sql.Stmt {
prepared.mu.RLock()
defer prepared.mu.RUnlock()
return prepared.stmts[query]
}
func (prepared *PreparedDBTX) ExecContext(ctx context.Context, query string, arguments ...any) (sql.Result, error) {
statement := prepared.cachedStatement(query)
if statement == nil {
return prepared.db.ExecContext(ctx, query, arguments...)
}
return statement.ExecContext(ctx, arguments...)
}
func (prepared *PreparedDBTX) QueryContext(ctx context.Context, query string, arguments ...any) (*sql.Rows, error) {
statement := prepared.cachedStatement(query)
if statement == nil {
return prepared.db.QueryContext(ctx, query, arguments...)
}
return statement.QueryContext(ctx, arguments...)
}
func (prepared *PreparedDBTX) QueryRowContext(ctx context.Context, query string, arguments ...any) *sql.Row {
statement := prepared.cachedStatement(query)
if statement == nil {
return prepared.db.QueryRowContext(ctx, query, arguments...)
}
return statement.QueryRowContext(ctx, arguments...)
}
func (prepared *PreparedDBTX) BeginTx(ctx context.Context, options *sql.TxOptions) (*sql.Tx, error) {
return prepared.db.BeginTx(ctx, options)
}
func (prepared *PreparedDBTX) PingContext(ctx context.Context) error {
return prepared.db.PingContext(ctx)
}
func (prepared *PreparedDBTX) UnderlyingDB() *sql.DB {
return prepared.db
}
func (prepared *PreparedDBTX) Close() error {
prepared.mu.Lock()
defer prepared.mu.Unlock()
err := sglCloseStatements(prepared.stmts)
prepared.stmts = make(map[string]*sql.Stmt)
return err
}
func (prepared *PreparedDBTX) WrapDBTX(inner any) any {
transaction, ok := inner.(*sql.Tx)
if !ok {
return inner
}
return &preparedTx{source: prepared, transaction: transaction}
}
type preparedTx struct {
source *PreparedDBTX
transaction *sql.Tx
}
func (bound *preparedTx) ExecContext(ctx context.Context, query string, arguments ...any) (sql.Result, error) {
statement := bound.source.cachedStatement(query)
if statement == nil {
return bound.transaction.ExecContext(ctx, query, arguments...)
}
return bound.transaction.StmtContext(ctx, statement).ExecContext(ctx, arguments...)
}
func (bound *preparedTx) QueryContext(ctx context.Context, query string, arguments ...any) (*sql.Rows, error) {
statement := bound.source.cachedStatement(query)
if statement == nil {
return bound.transaction.QueryContext(ctx, query, arguments...)
}
return bound.transaction.StmtContext(ctx, statement).QueryContext(ctx, arguments...)
}
func (bound *preparedTx) QueryRowContext(ctx context.Context, query string, arguments ...any) *sql.Row {
statement := bound.source.cachedStatement(query)
if statement == nil {
return bound.transaction.QueryRowContext(ctx, query, arguments...)
}
return bound.transaction.StmtContext(ctx, statement).QueryRowContext(ctx, arguments...)
}
func sglCloseStatements(stmts map[string]*sql.Stmt) error {
var closeErrors []error
for _, statement := range stmts {
if err := statement.Close(); err != nil {
closeErrors = append(closeErrors, err)
}
}
return errors.Join(closeErrors...)
}
internal/store/querier.go
// Code generated by sgl. DO NOT EDIT.
package store
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
type dbtxWrapper interface {
WrapDBTX(inner any) any
}
type Queries struct {
reader DBTX
writer DBTX
wrapper dbtxWrapper
}
func New(db DBTX) *Queries {
wrapper, _ := db.(dbtxWrapper)
return &Queries{reader: db, writer: db, wrapper: wrapper}
}
func NewWithReplica(writer DBTX, reader DBTX) *Queries {
wrapper, _ := writer.(dbtxWrapper)
return &Queries{reader: reader, writer: writer, wrapper: wrapper}
}
func (queries *Queries) WithTx(transaction *sql.Tx) *Queries {
if queries == nil {
return &Queries{reader: transaction, writer: transaction, wrapper: nil}
}
db := queries.wrapDBTX(transaction)
return &Queries{reader: db, writer: db, wrapper: queries.wrapper}
}
func (queries *Queries) wrapDBTX(db DBTX) DBTX {
if queries.wrapper == nil {
return db
}
wrapped, ok := queries.wrapper.WrapDBTX(db).(DBTX)
if !ok {
return db
}
return wrapped
}
func (queries *Queries) RunInTx(ctx context.Context, db *sql.DB, fn func(*Queries) error) error {
transaction, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer transaction.Rollback()
if err = fn(queries.WithTx(transaction)); err != nil {
return err
}
return transaction.Commit()
}
internal/store/value_reading.go
// Code generated by sgl. DO NOT EDIT.
package store
import (
"fmt"
"reflect"
)
type sglOrderedMap interface {
Keys() []any
Values() []any
}
func sglReadList(target reflect.Value, value reflect.Value, read func(reflect.Value, any) error) error {
if value.Kind() != reflect.Slice && value.Kind() != reflect.Array {
return sglUnreadable(value.Type(), target.Type())
}
if value.Kind() == reflect.Slice && value.IsNil() {
target.SetZero()
return nil
}
list := reflect.MakeSlice(target.Type(), value.Len(), value.Len())
for index := range value.Len() {
if err := read(list.Index(index), value.Index(index).Interface()); err != nil {
return fmt.Errorf("element %d: %w", index, err)
}
}
target.Set(list)
return nil
}
func sglReadMap(target reflect.Value, value reflect.Value, read func(reflect.Value, any) error) error {
if value.Kind() == reflect.Map && value.IsNil() {
target.SetZero()
return nil
}
keys, values, ok := sglMapEntries(value)
if !ok {
return sglUnreadable(value.Type(), target.Type())
}
converted := reflect.MakeMapWithSize(target.Type(), len(keys))
for index, key := range keys {
convertedKey := reflect.New(target.Type().Key()).Elem()
if err := read(convertedKey, key); err != nil {
return fmt.Errorf("key %v: %w", key, err)
}
element := reflect.New(target.Type().Elem()).Elem()
if err := read(element, values[index]); err != nil {
return fmt.Errorf("value of key %v: %w", key, err)
}
converted.SetMapIndex(convertedKey, element)
}
target.Set(converted)
return nil
}
func sglMapEntries(value reflect.Value) (keys []any, values []any, ok bool) {
if value.Kind() == reflect.Map {
keys = make([]any, 0, value.Len())
values = make([]any, 0, value.Len())
entries := value.MapRange()
for entries.Next() {
keys = append(keys, entries.Key().Interface())
values = append(values, entries.Value().Interface())
}
return keys, values, true
}
pointer := reflect.New(value.Type())
pointer.Elem().Set(value)
ordered, ordersEntries := reflect.TypeAssert[sglOrderedMap](pointer)
if !ordersEntries {
return nil, nil, false
}
keys, values = ordered.Keys(), ordered.Values()
return keys, values, len(keys) == len(values)
}
func sglReadScalar(target reflect.Value, value reflect.Value) error {
kind := sglScalarKind(value.Kind())
if kind == "" || kind != sglScalarKind(target.Kind()) || !value.CanConvert(target.Type()) {
return sglUnreadable(value.Type(), target.Type())
}
converted := value.Convert(target.Type())
if !sglScalarHolds(converted, value) {
return fmt.Errorf("%v overflows %s", value.Interface(), target.Type())
}
target.Set(converted)
return nil
}
func sglScalarHolds(converted reflect.Value, value reflect.Value) bool {
switch {
case !value.CanInt() && !value.CanUint():
return true
case !converted.Convert(value.Type()).Equal(value):
return false
case value.CanInt() && converted.CanUint():
return value.Int() >= 0
case value.CanUint() && converted.CanInt():
return converted.Int() >= 0
default:
return true
}
}
func sglScalarKind(kind reflect.Kind) string {
switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "integer"
case reflect.Float32, reflect.Float64:
return "float"
case reflect.String:
return "string"
case reflect.Bool:
return "boolean"
default:
return ""
}
}
func sglUnreadable(source reflect.Type, target reflect.Type) error {
return fmt.Errorf("cannot read %s into %s", source, target)
}
output.txt
out of date: driver_composites.go, driver_values.go, models.go, notes.sql.go, prepared.go, querier.go, value_reading.go
wrote 7 files for 2 queries
up to date