A custom emitter
On this page
On SQLite:
- the wrapper forwards emit.TypeDefaulterPort, which the generator looks for on the emitter it was given
- the extra file is built from emit.Request's analysed queries and formatted with go/format
- the committed internal/store comes from go run . -write; the stock sgl generate would prune query_index.go, which only this emitter writes (053 builds an sgl that keeps it)
Seagull splits its work in two. The analyser reads the SQL and works out everything about each query; an emitter turns that analysis into source files. The emitters Seagull ships write Go for database/sql, pgx and ClickHouse's native driver, and the interface they implement is public, so you can write your own: for another driver, another language, or, as here, one extra file beside the usual package.
This example wraps the database/sql emitter and adds query_index.go, a list of every query in the package with its command and whether it only reads. A list like that can feed a documentation page, metric labels or an admin screen, and it can never fall out of step with the SQL.
The interface
An emitter has one method:
type EmitterPort interface {
Emit(ctx context.Context, request *emit.Request) ([]emit.GeneratedFile, error)
}
emit.Request carries everything the analysis produced: the package name, the analysed queries in file order, the schema catalogue, the type mapping with every override applied, the dialect, and the Style that names things in the generated code. The emitter returns one GeneratedFile per file, a plain name and its full content.
The program
example.go
package main
import (
"context"
"fmt"
"go/format"
"io"
"io/fs"
"strings"
"seagull.sh/seagull"
"seagull.sh/seagull/sdk/dialect"
"seagull.sh/seagull/sdk/dialect/dialect_engine_sqlite"
"seagull.sh/seagull/sdk/emit"
"seagull.sh/seagull/sdk/emit/emit_driver_databasesql"
"seagull.sh/seagull/sdk/emit/emit_kit"
)
const (
// indexFile is the name of the file the emitter adds.
indexFile = "query_index.go"
// infoType is the type the index declares for one query.
infoType = "QueryInfo"
// indexVariable is the variable the index declares.
indexVariable = "QueryIndex"
)
// indexEmitter wraps another emitter and adds query_index.go: a list of every query in
// the package with its method, its command and whether it only reads, for a documentation
// page, metric labels or an admin screen. It passes on every optional interface the
// generator asks the emitter it was given for, adding the names its own file declares to
// the wrapped emitter's reservation.
type indexEmitter struct {
// base generates the package itself.
base emit.EmitterPort
}
var (
_ emit.EmitterPort = (*indexEmitter)(nil)
_ emit.TypeDefaulterPort = (*indexEmitter)(nil)
_ emit.ReserverPort = (*indexEmitter)(nil)
_ emit.CheckerPort = (*indexEmitter)(nil)
_ emit.NullElementBinderPort = (*indexEmitter)(nil)
)
// Emit generates the wrapped emitter's files and appends the index.
//
// Takes request (*emit.Request) which carries the analysed queries and the style.
//
// Returns []emit.GeneratedFile which holds the wrapped emitter's files and the index.
// Returns error when either cannot be generated.
func (e indexEmitter) Emit(ctx context.Context, request *emit.Request) ([]emit.GeneratedFile, error) {
files, err := e.base.Emit(ctx, request)
if err != nil {
return nil, err
}
index, err := queryIndex(request)
if err != nil {
return nil, err
}
return append(files, index), nil
}
// DefaultTypes passes on the wrapped emitter's type defaults. The generator asks the
// emitter it was given whether it implements emit.TypeDefaulterPort, so without this
// method the wrapper would hide them.
//
// Takes info (*dialect.Facts) which describes the dialect.
//
// Returns seagull.TypeSlots which holds the wrapped emitter's defaults, or none.
func (e indexEmitter) DefaultTypes(info *dialect.Facts) seagull.TypeSlots {
if defaulter, ok := e.base.(emit.TypeDefaulterPort); ok {
return defaulter.DefaultTypes(info)
}
return seagull.TypeSlots{}
}
// Reserve passes on the names the wrapped emitter's code uses and adds the index's, so
// the naming plan never gives a model, a named type or a query's constant the name
// QueryInfo or QueryIndex, and never names a query file query_index.go.
//
// Takes info (*dialect.Facts) which describes the dialect.
// Takes style (*emit.Style) which holds the style of the generated code.
//
// Returns emit.Reservation which holds the wrapped emitter's names and the index's.
func (e indexEmitter) Reserve(info *dialect.Facts, style *emit.Style) emit.Reservation {
reservation := emit_kit.ReservationFor(e.base, info, style)
reservation.Declarations = append(reservation.Declarations, infoType, indexVariable)
reservation.Files = append(reservation.Files, indexFile)
return reservation
}
// Check passes on what the wrapped emitter refuses, which the generator reports as
// diagnostics at the query before it emits anything.
//
// Takes request (*emit.Request) which is the request about to be emitted.
//
// Returns []emit.Problem which holds the wrapped emitter's problems, or none.
func (e indexEmitter) Check(request *emit.Request) []emit.Problem {
if checker, ok := e.base.(emit.CheckerPort); ok {
return checker.Check(request)
}
return nil
}
// BindsNullElements passes on whether the wrapped emitter's driver binds a NULL element
// of an array parameter. Without it the generator would take the wrapper for one that
// does, and give array parameters nullable elements on a driver that panics on them, such
// as DuckDB's.
//
// Takes info (*dialect.Facts) which describes the dialect.
//
// Returns bool which is the wrapped emitter's answer, or true when it gives none.
func (e indexEmitter) BindsNullElements(info *dialect.Facts) bool {
if binder, ok := e.base.(emit.NullElementBinderPort); ok {
return binder.BindsNullElements(info)
}
return true
}
// queryIndex writes query_index.go. It starts with the style's header, so WriteFiles
// recognises it as generated, and is formatted with go/format. Each query's method is the
// name the naming plan gives it, which the wrapped emitter declares, rather than one
// derived from the query's name.
//
// Takes request (*emit.Request) which carries the package name, queries, naming plan and
// style.
//
// Returns emit.GeneratedFile which is the index.
// Returns error when the source does not format.
func queryIndex(request *emit.Request) (emit.GeneratedFile, error) {
var source strings.Builder
fmt.Fprintf(&source, "%s\n\npackage %s\n\n", request.Style.Header, request.Package)
fmt.Fprintf(&source, "// %s describes one generated query method.\n", infoType)
fmt.Fprintf(&source, "type %s struct {\n\tName string\n\tMethod string\n\tCommand string\n\tReadOnly bool\n}\n\n", infoType)
fmt.Fprintf(&source, "// %s lists every query of the package in source order.\n", indexVariable)
fmt.Fprintf(&source, "var %s = []%s{\n", indexVariable, infoType)
for index, query := range request.Queries {
fmt.Fprintf(&source, "\t{Name: %q, Method: %q, Command: %q, ReadOnly: %t},\n",
query.Name, request.Naming.Queries[index].Method, query.Command, query.ReadOnly)
}
source.WriteString("}\n")
formatted, err := format.Source([]byte(source.String()))
if err != nil {
return emit.GeneratedFile{}, fmt.Errorf("formatting %s: %w", indexFile, err)
}
return emit.GeneratedFile{Name: indexFile, Content: formatted}, nil
}
// generate runs the generator with the index emitter wrapped around the database/sql
// emitter.
//
// Takes project (fs.FS) which holds db/migrations and db/queries.
//
// Returns *seagull.Result which holds the generated files.
// Returns error when generation fails or a query has an error diagnostic.
func generate(ctx context.Context, project fs.FS) (*seagull.Result, error) {
emitter := indexEmitter{base: emit_driver_databasesql.New(emit_driver_databasesql.WithPrepared(false))}
generator, err := seagull.New(dialect_engine_sqlite.New(), seagull.WithEmitter(emitter))
if err != nil {
return nil, err
}
result, err := generator.Generate(ctx, seagull.Input{
Migrations: project,
Queries: nil,
Package: "store",
MigrationDir: "db/migrations",
QueryDir: "db/queries",
})
if err != nil {
return nil, err
}
if err := result.Err(); err != nil {
return nil, err
}
return result, nil
}
// run generates the package with the index emitter and prints the files it produced and
// the index itself.
//
// Takes project (fs.FS) which holds db/migrations and db/queries.
// Takes output (io.Writer) which receives the file list and the index.
//
// Returns error when generation fails.
func run(ctx context.Context, project fs.FS, output io.Writer) error {
result, err := generate(ctx, project)
if err != nil {
return err
}
for _, file := range result.Files {
fmt.Fprintln(output, file.Name)
if file.Name == indexFile {
fmt.Fprintf(output, "\n%s", file.Content)
}
}
return nil
}
Wrapping. indexEmitter holds another emitter, here emit_driver_databasesql.New(emit_driver_databasesql.WithPrepared(false)). Its Emit lets that emitter generate the package, then appends one more file. Everything the wrapped emitter writes is untouched.
Forwarding the type defaults. An emitter may also implement emit.TypeDefaulterPort, to supply the Go types its driver scans into. The generator asks the emitter it was given whether it implements that interface, so a wrapper that did not forward DefaultTypes would hide the wrapped emitter's defaults, and the generated types could quietly change.
Building the file. queryIndex writes ordinary Go source from request.Queries, reading each query's Name, Command and ReadOnly. Two details make it behave like any other generated file. It starts with request.Style.Header, the generated-file header, which is how WriteFiles recognises the files it may prune later. And it is formatted with go/format, so it reads like the rest of the package.
Using it. seagull.WithEmitter replaces the default emitter, and Generate returns the extra file in Result.Files with all the others.
What it prints
The program lists the files the emitter produced, then prints the index:
models.go
tasks.sql.go
querier.go
query_index.go
// Code generated by sgl. DO NOT EDIT.
package store
// QueryInfo describes one generated query method.
type QueryInfo struct {
Name string
Method string
Command string
ReadOnly bool
}
// QueryIndex lists every query of the package in source order.
var QueryIndex = []QueryInfo{
{Name: "CreateTask", Method: "CreateTask", Command: "one", ReadOnly: false},
{Name: "ListTasks", Method: "ListTasks", Command: "many", ReadOnly: true},
{Name: "CompleteTask", Method: "CompleteTask", Command: "execrows", ReadOnly: false},
}
The index uses the same header as the rest of the package, and ListTasks is the only query marked read-only. The three files before it are exactly what the database/sql emitter writes without the wrapper.
The generated method for ListTasks is unchanged by the wrapper:
internal/store
type ListTasksRow struct {
ID int64 `json:"id"`
Title string `json:"title"`
Done int64 `json:"done"`
}
func (queries *Queries) ListTasks(ctx context.Context) ([]ListTasksRow, error) {
rows, err := queries.reader.QueryContext(ctx, listTasksSQL)
if err != nil {
return nil, err
}
defer rows.Close()
var results []ListTasksRow
if rows.Next() {
var row ListTasksRow
scanArgs := []any{&row.ID, &row.Title, &row.Done}
for more := true; more; more = rows.Next() {
row = ListTasksRow{}
if err := rows.Scan(scanArgs...); err != nil {
return nil, err
}
results = append(results, row)
}
}
if err := rows.Err(); err != nil {
return nil, err
}
return results, nil
}
Keeping the file
go run . -write writes the package into internal/store with WriteFiles, and that is how the committed copy was made. Notice what would happen if you ran the stock sgl generate here: query_index.go carries sgl's header, but the stock emitter does not produce it, so sgl would prune it as a stale generated file. To keep it under sgl, the emitter has to be part of sgl itself, which is what Your own sgl builds.
Writing an emitter describes every field of the request and how to choose Go types for a new driver, and the emit package documents the interface.
Run it locally
From the directory you cloned the Seagull repository into:
cd seagull/examples/embedding && go run ./052_custom_emitter
Its test runs it too, and checks what it prints against output.txt:
cd seagull/examples/embedding && go test ./052_custom_emitter
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: tasks
dialect: sqlite
migrations: db/migrations
queries: db/queries
go:
package: store
out: internal/store
db/migrations/0001_init.up.sql
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0
);
db/migrations/0001_init.down.sql
DROP TABLE tasks;
db/queries/tasks.sql
-- sgl.query(CreateTask, one)
INSERT INTO tasks (title) VALUES (?) RETURNING id, title, done;
-- sgl.query(ListTasks, many)
SELECT id, title, done FROM tasks ORDER BY id;
-- sgl.query(CompleteTask, execrows)
UPDATE tasks SET done = 1 WHERE id = ?;
example.go
package main
import (
"context"
"fmt"
"go/format"
"io"
"io/fs"
"strings"
"seagull.sh/seagull"
"seagull.sh/seagull/sdk/dialect"
"seagull.sh/seagull/sdk/dialect/dialect_engine_sqlite"
"seagull.sh/seagull/sdk/emit"
"seagull.sh/seagull/sdk/emit/emit_driver_databasesql"
"seagull.sh/seagull/sdk/emit/emit_kit"
)
const (
// indexFile is the name of the file the emitter adds.
indexFile = "query_index.go"
// infoType is the type the index declares for one query.
infoType = "QueryInfo"
// indexVariable is the variable the index declares.
indexVariable = "QueryIndex"
)
// indexEmitter wraps another emitter and adds query_index.go: a list of every query in
// the package with its method, its command and whether it only reads, for a documentation
// page, metric labels or an admin screen. It passes on every optional interface the
// generator asks the emitter it was given for, adding the names its own file declares to
// the wrapped emitter's reservation.
type indexEmitter struct {
// base generates the package itself.
base emit.EmitterPort
}
var (
_ emit.EmitterPort = (*indexEmitter)(nil)
_ emit.TypeDefaulterPort = (*indexEmitter)(nil)
_ emit.ReserverPort = (*indexEmitter)(nil)
_ emit.CheckerPort = (*indexEmitter)(nil)
_ emit.NullElementBinderPort = (*indexEmitter)(nil)
)
// Emit generates the wrapped emitter's files and appends the index.
//
// Takes request (*emit.Request) which carries the analysed queries and the style.
//
// Returns []emit.GeneratedFile which holds the wrapped emitter's files and the index.
// Returns error when either cannot be generated.
func (e indexEmitter) Emit(ctx context.Context, request *emit.Request) ([]emit.GeneratedFile, error) {
files, err := e.base.Emit(ctx, request)
if err != nil {
return nil, err
}
index, err := queryIndex(request)
if err != nil {
return nil, err
}
return append(files, index), nil
}
// DefaultTypes passes on the wrapped emitter's type defaults. The generator asks the
// emitter it was given whether it implements emit.TypeDefaulterPort, so without this
// method the wrapper would hide them.
//
// Takes info (*dialect.Facts) which describes the dialect.
//
// Returns seagull.TypeSlots which holds the wrapped emitter's defaults, or none.
func (e indexEmitter) DefaultTypes(info *dialect.Facts) seagull.TypeSlots {
if defaulter, ok := e.base.(emit.TypeDefaulterPort); ok {
return defaulter.DefaultTypes(info)
}
return seagull.TypeSlots{}
}
// Reserve passes on the names the wrapped emitter's code uses and adds the index's, so
// the naming plan never gives a model, a named type or a query's constant the name
// QueryInfo or QueryIndex, and never names a query file query_index.go.
//
// Takes info (*dialect.Facts) which describes the dialect.
// Takes style (*emit.Style) which holds the style of the generated code.
//
// Returns emit.Reservation which holds the wrapped emitter's names and the index's.
func (e indexEmitter) Reserve(info *dialect.Facts, style *emit.Style) emit.Reservation {
reservation := emit_kit.ReservationFor(e.base, info, style)
reservation.Declarations = append(reservation.Declarations, infoType, indexVariable)
reservation.Files = append(reservation.Files, indexFile)
return reservation
}
// Check passes on what the wrapped emitter refuses, which the generator reports as
// diagnostics at the query before it emits anything.
//
// Takes request (*emit.Request) which is the request about to be emitted.
//
// Returns []emit.Problem which holds the wrapped emitter's problems, or none.
func (e indexEmitter) Check(request *emit.Request) []emit.Problem {
if checker, ok := e.base.(emit.CheckerPort); ok {
return checker.Check(request)
}
return nil
}
// BindsNullElements passes on whether the wrapped emitter's driver binds a NULL element
// of an array parameter. Without it the generator would take the wrapper for one that
// does, and give array parameters nullable elements on a driver that panics on them, such
// as DuckDB's.
//
// Takes info (*dialect.Facts) which describes the dialect.
//
// Returns bool which is the wrapped emitter's answer, or true when it gives none.
func (e indexEmitter) BindsNullElements(info *dialect.Facts) bool {
if binder, ok := e.base.(emit.NullElementBinderPort); ok {
return binder.BindsNullElements(info)
}
return true
}
// queryIndex writes query_index.go. It starts with the style's header, so WriteFiles
// recognises it as generated, and is formatted with go/format. Each query's method is the
// name the naming plan gives it, which the wrapped emitter declares, rather than one
// derived from the query's name.
//
// Takes request (*emit.Request) which carries the package name, queries, naming plan and
// style.
//
// Returns emit.GeneratedFile which is the index.
// Returns error when the source does not format.
func queryIndex(request *emit.Request) (emit.GeneratedFile, error) {
var source strings.Builder
fmt.Fprintf(&source, "%s\n\npackage %s\n\n", request.Style.Header, request.Package)
fmt.Fprintf(&source, "// %s describes one generated query method.\n", infoType)
fmt.Fprintf(&source, "type %s struct {\n\tName string\n\tMethod string\n\tCommand string\n\tReadOnly bool\n}\n\n", infoType)
fmt.Fprintf(&source, "// %s lists every query of the package in source order.\n", indexVariable)
fmt.Fprintf(&source, "var %s = []%s{\n", indexVariable, infoType)
for index, query := range request.Queries {
fmt.Fprintf(&source, "\t{Name: %q, Method: %q, Command: %q, ReadOnly: %t},\n",
query.Name, request.Naming.Queries[index].Method, query.Command, query.ReadOnly)
}
source.WriteString("}\n")
formatted, err := format.Source([]byte(source.String()))
if err != nil {
return emit.GeneratedFile{}, fmt.Errorf("formatting %s: %w", indexFile, err)
}
return emit.GeneratedFile{Name: indexFile, Content: formatted}, nil
}
// generate runs the generator with the index emitter wrapped around the database/sql
// emitter.
//
// Takes project (fs.FS) which holds db/migrations and db/queries.
//
// Returns *seagull.Result which holds the generated files.
// Returns error when generation fails or a query has an error diagnostic.
func generate(ctx context.Context, project fs.FS) (*seagull.Result, error) {
emitter := indexEmitter{base: emit_driver_databasesql.New(emit_driver_databasesql.WithPrepared(false))}
generator, err := seagull.New(dialect_engine_sqlite.New(), seagull.WithEmitter(emitter))
if err != nil {
return nil, err
}
result, err := generator.Generate(ctx, seagull.Input{
Migrations: project,
Queries: nil,
Package: "store",
MigrationDir: "db/migrations",
QueryDir: "db/queries",
})
if err != nil {
return nil, err
}
if err := result.Err(); err != nil {
return nil, err
}
return result, nil
}
// run generates the package with the index emitter and prints the files it produced and
// the index itself.
//
// Takes project (fs.FS) which holds db/migrations and db/queries.
// Takes output (io.Writer) which receives the file list and the index.
//
// Returns error when generation fails.
func run(ctx context.Context, project fs.FS, output io.Writer) error {
result, err := generate(ctx, project)
if err != nil {
return err
}
for _, file := range result.Files {
fmt.Fprintln(output, file.Name)
if file.Name == indexFile {
fmt.Fprintf(output, "\n%s", file.Content)
}
}
return nil
}
main.go
// Command 052_custom_emitter generates this example's store package with an emitter that
// wraps the database/sql one and adds query_index.go, and prints the result.
//
// Run it with `go run .`, or `go run . -write` to write the package into internal/store,
// where it is committed so the build checks the generated code.
package main
import (
"context"
"flag"
"log"
"os"
"seagull.sh/seagull"
)
// outputDirectory is where -write puts the generated package.
const outputDirectory = "internal/store"
func main() {
write := flag.Bool("write", false, "write the package into internal/store instead of printing it")
flag.Parse()
if err := execute(context.Background(), *write); err != nil {
log.Fatal(err)
}
}
// execute prints the generated package, or writes it into internal/store.
//
// Takes write (bool) which writes instead of printing.
//
// Returns error when generation or writing fails.
func execute(ctx context.Context, write bool) error {
project := os.DirFS(".")
if !write {
return run(ctx, project, os.Stdout)
}
result, err := generate(ctx, project)
if err != nil {
return err
}
return seagull.WriteFiles(outputDirectory, result)
}
internal/store/models.go
// Code generated by sgl. DO NOT EDIT.
package store
type Tasks struct {
ID int64 `json:"id"`
Title string `json:"title"`
Done int64 `json:"done"`
}
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/query_index.go
// Code generated by sgl. DO NOT EDIT.
package store
// QueryInfo describes one generated query method.
type QueryInfo struct {
Name string
Method string
Command string
ReadOnly bool
}
// QueryIndex lists every query of the package in source order.
var QueryIndex = []QueryInfo{
{Name: "CreateTask", Method: "CreateTask", Command: "one", ReadOnly: false},
{Name: "ListTasks", Method: "ListTasks", Command: "many", ReadOnly: true},
{Name: "CompleteTask", Method: "CompleteTask", Command: "execrows", ReadOnly: false},
}
internal/store/tasks.sql.go
// Code generated by sgl. DO NOT EDIT.
package store
import "context"
const completeTaskSQL = `UPDATE tasks SET done = 1 WHERE id = ?1;`
func (queries *Queries) CompleteTask(ctx context.Context, id int64) (int64, error) {
results, err := queries.writer.ExecContext(ctx, completeTaskSQL, id)
if err != nil {
return 0, err
}
return results.RowsAffected()
}
const createTaskSQL = `INSERT INTO tasks (title) VALUES (?1) RETURNING id, title, done;`
type CreateTaskRow struct {
ID int64 `json:"id"`
Title string `json:"title"`
Done int64 `json:"done"`
}
func (queries *Queries) CreateTask(ctx context.Context, title string) (CreateTaskRow, error) {
var row CreateTaskRow
err := queries.writer.QueryRowContext(ctx, createTaskSQL, title).Scan(&row.ID, &row.Title, &row.Done)
if err != nil {
return CreateTaskRow{}, err
}
return row, nil
}
const listTasksSQL = `SELECT id, title, done FROM tasks ORDER BY id;`
type ListTasksRow struct {
ID int64 `json:"id"`
Title string `json:"title"`
Done int64 `json:"done"`
}
func (queries *Queries) ListTasks(ctx context.Context) ([]ListTasksRow, error) {
rows, err := queries.reader.QueryContext(ctx, listTasksSQL)
if err != nil {
return nil, err
}
defer rows.Close()
var results []ListTasksRow
if rows.Next() {
var row ListTasksRow
scanArgs := []any{&row.ID, &row.Title, &row.Done}
for more := true; more; more = rows.Next() {
row = ListTasksRow{}
if err := rows.Scan(scanArgs...); err != nil {
return nil, err
}
results = append(results, row)
}
}
if err := rows.Err(); err != nil {
return nil, err
}
return results, nil
}
output.txt
models.go
tasks.sql.go
querier.go
query_index.go
// Code generated by sgl. DO NOT EDIT.
package store
// QueryInfo describes one generated query method.
type QueryInfo struct {
Name string
Method string
Command string
ReadOnly bool
}
// QueryIndex lists every query of the package in source order.
var QueryIndex = []QueryInfo{
{Name: "CreateTask", Method: "CreateTask", Command: "one", ReadOnly: false},
{Name: "ListTasks", Method: "ListTasks", Command: "many", ReadOnly: true},
{Name: "CompleteTask", Method: "CompleteTask", Command: "execrows", ReadOnly: false},
}