Enums as generated Go types, and your own types with go_type

On this page

On MySQL:

  • ENUM('fish', 'sides', 'puddings') is declared on the column itself; ORDER BY sorts its values in declaration order
  • Each ENUM becomes a named string type in models.go, named after its table and column (store.MenuItemsSection), with a constant per value and a Valid method
  • An inline ENUM has no type name for a sql: override, so a column override in types.overrides gives menu_items.status your own type, in models.go, rows and the parameters bound to it
  • sgl.column(section, go_type: ...) changes only the column ItemsInSection returns; its parameter stays the generated store.MenuItemsSection
  • Strict mode refuses a value the ENUM does not list, reporting it as data truncated

An enum column accepts only the labels it declares, and the database refuses anything else. Go has no enum type for Seagull to map that to, so by default an enum column arrives as a plain string: correct, but it says nothing about which strings are allowed. Seagull does not invent enum types of its own. You write the Go type you want, with a constant per label and any methods you like, and point the column at it with a go_type override. The generated code then hands you that type instead of a string.

This example is a chip shop menu. Each item has a section and a status, and both are enums.

The schema

MySQL declares an ENUM on the column itself, listing its labels.

db/migrations/0001_menu.up.sql

CREATE TABLE menu_items (
    id      BIGINT                              NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name    VARCHAR(50)                         NOT NULL UNIQUE,
    section ENUM('fish', 'sides', 'puddings')   NOT NULL,
    status  ENUM('available', 'sold_out')       NOT NULL DEFAULT 'available'
);

Your own types

The program's own package, internal/menu, declares type Section string and type Status string, with a constant for each label: Fish, Sides and Puddings, then Available and SoldOut. The example points the columns at them in the two places an override can live.

For every query, sgl.yaml names the column in types.overrides. Any query that returns menu_items.status as it is gets a menu.Status:

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: menu
    dialect: mysql
    migrations: db/migrations
    queries: db/queries
    go:
      package: store
      out: internal/store
      types:
        overrides:
          - column: menu_items.status
            go: seagull.sh/seagull/examples/mysql/007_enums/internal/menu.Status

For one query, an sgl.column directive in the query's header does the same, here for section:

db/queries/menu.sql

-- sgl.query(ItemsInSection, many)
-- sgl.column(section, go_type: "seagull.sh/seagull/examples/mysql/007_enums/internal/menu.Section")
SELECT name, section, status FROM menu_items WHERE section = ? ORDER BY name;

So the two queries differ. ListMenu has no directive, which leaves Section a plain string, while its Status is typed by sgl.yaml:

internal/store

type ListMenuRow struct {
	Name    string           `json:"name"`
	Section MenuItemsSection `json:"section"`
	Status  menu.Status      `json:"status"`
}

type MenuItemsSection string

func (queries *Queries) ListMenu(ctx context.Context) ([]ListMenuRow, error) {
	rows, err := queries.reader.QueryContext(ctx, listMenuSQL)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var results []ListMenuRow
	for rows.Next() {
		var row ListMenuRow
		if err := rows.Scan(&row.Name, &row.Section, &row.Status); err != nil {
			return nil, err
		}
		results = append(results, row)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return results, nil
}

ItemsInSection has both, and the generated file imports the package for you:

internal/store

type ItemsInSectionRow struct {
	Name    string       `json:"name"`
	Section menu.Section `json:"section"`
	Status  menu.Status  `json:"status"`
}

type MenuItemsSection string

func (queries *Queries) ItemsInSection(ctx context.Context, section MenuItemsSection) ([]ItemsInSectionRow, error) {
	rows, err := queries.reader.QueryContext(ctx, itemsInSectionSQL, section)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var results []ItemsInSectionRow
	for rows.Next() {
		var row ItemsInSectionRow
		if err := rows.Scan(&row.Name, &row.Section, &row.Status); err != nil {
			return nil, err
		}
		results = append(results, row)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return results, nil
}

Parameters stay strings

An override changes the columns a query returns, not what it takes.

models.go and every Params field keep string, and so does the section that ItemsInSection filters on, so the program converts on the way in: string(menu.Fish).

The database still checks

A Go type with constants does not stop a caller passing any string it likes, but the enum does: the database refuses a label it does not list when the statement runs. The program tries it on purpose and prints the refusal.

Using it

example.go

package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"io"

	"seagull.sh/seagull/examples/mysql/007_enums/internal/menu"
	"seagull.sh/seagull/examples/mysql/007_enums/internal/store"
)

// run fills a chip shop menu whose section and status columns are MySQL ENUMs, marks an
// item sold out and reads the menu back. Each ENUM becomes a named Go type in the
// generated package unless an override names a type of your own:
//
//   - section has no override, so models.go declares store.MenuItemsSection, named after
//     the table and column that hold the ENUM, with a constant for each value, such as
//     store.MenuItemsSectionFish, and a Valid method; ListMenuRow.Section,
//     AddItemParams.Section and ItemsInSection's parameter are all a
//     store.MenuItemsSection;
//   - sgl.yaml's types.overrides names the column menu_items.status, so every query that
//     returns it as it is gives menu.Status, as in ListMenuRow.Status, and so does every
//     parameter compared with or assigned to it, as in SetStatusParams.Status; models.go
//     uses it too, and the generated package declares no type for that ENUM;
//   - sgl.column(section, go_type: ...) in ItemsInSection gives that one query's returned
//     Section the type menu.Section; the parameter it compares with stays a
//     store.MenuItemsSection.
//
// An ENUM sorts in the order its values are declared, not alphabetically, so ORDER BY
// section lists fish, then sides, then puddings. Valid reports whether a value is one the
// ENUM declares, so the shop can check a section before it binds it. In strict mode, the
// default since MySQL 5.7, a value the ENUM does not list is refused as it is stored as
// well; MySQL keeps an ENUM as the index of its value, and its error says the value was
// truncated.
//
// Takes database (*sql.DB) which is a migrated database.
// Takes output (io.Writer) which receives the menu, the fish section and the refusal.
//
// Returns error when a query fails other than as the example expects.
func run(ctx context.Context, database *sql.DB, output io.Writer) error {
	queries := store.New(database)

	items := []store.AddItemParams{
		{Name: "cod", Section: store.MenuItemsSectionFish},
		{Name: "haddock", Section: store.MenuItemsSectionFish},
		{Name: "chips", Section: store.MenuItemsSectionSides},
		{Name: "mushy peas", Section: store.MenuItemsSectionSides},
		{Name: "sticky toffee pudding", Section: store.MenuItemsSectionPuddings},
	}
	for _, item := range items {
		if err := queries.AddItem(ctx, item); err != nil {
			return fmt.Errorf("adding %s: %w", item.Name, err)
		}
	}
	if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: menu.SoldOut, Name: "haddock"}); err != nil {
		return fmt.Errorf("selling out of haddock: %w", err)
	}

	listed, err := queries.ListMenu(ctx)
	if err != nil {
		return fmt.Errorf("listing the menu: %w", err)
	}
	var menuRow store.ListMenuRow
	fmt.Fprintf(output, "ListMenu: section %T, status %T\n", menuRow.Section, menuRow.Status)
	for _, item := range listed {
		fmt.Fprintf(output, "  %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))
	}

	fish, err := queries.ItemsInSection(ctx, store.MenuItemsSectionFish)
	if err != nil {
		return fmt.Errorf("listing the fish: %w", err)
	}
	var sectionRow store.ItemsInSectionRow
	fmt.Fprintf(output, "ItemsInSection: section %T, status %T\n", sectionRow.Section, sectionRow.Status)
	for _, item := range fish {
		fmt.Fprintf(output, "  %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))
	}

	drinks := store.MenuItemsSection("drinks")
	fmt.Fprintf(output, "section drinks is valid: %t\n", drinks.Valid())
	err = queries.AddItem(ctx, store.AddItemParams{Name: "lemonade", Section: drinks})
	if err == nil {
		return errors.New("added an item to a section the enum does not list")
	}
	fmt.Fprintf(output, "refused section drinks: %v\n", err)
	return nil
}

// soldOut returns a note for an item that has sold out, comparing against the typed
// constant rather than a string.
//
// Takes status (menu.Status) which is the item's status.
//
// Returns string which is the note, or empty when the item is available.
func soldOut(status menu.Status) string {
	if status == menu.SoldOut {
		return " (sold out)"
	}
	return ""
}

What it prints

ListMenu: section store.MenuItemsSection, status menu.Status
  fish      cod
  fish      haddock (sold out)
  sides     chips
  sides     mushy peas
  puddings  sticky toffee pudding
ItemsInSection: section menu.Section, status menu.Status
  fish      cod
  fish      haddock (sold out)
section drinks is valid: false
refused section drinks: Error 1265 (01000): Data truncated for column 'section' at row 1

Type overrides covers go_type and the other override forms in more depth.

Across engines

Only these Go types of ItemsInSection differ between engines; the rest are the same everywhere.

MySQLDuckDB
section parameterMenuItemsSectionSection

MariaDB runs the MySQL version unchanged.

Compare the whole example side by side with DuckDB.

Where the example is missing:

  • Not on SQLite. SQLite has no enum type. Use a CHECK constraint and a go_type override.

Run it locally

From the directory you cloned the Seagull repository into:

cd seagull/examples/mysql && go run ./007_enums

Its test runs it too, and checks what it prints against output.txt:

cd seagull/examples/mysql && SGL_EXAMPLE_DSN="$DATABASE_URL" go test ./007_enums

Needs a running MySQL server: set DATABASE_URL, or pass -dsn, to the address of an empty database.

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: menu
    dialect: mysql
    migrations: db/migrations
    queries: db/queries
    go:
      package: store
      out: internal/store
      types:
        overrides:
          - column: menu_items.status
            go: seagull.sh/seagull/examples/mysql/007_enums/internal/menu.Status

db/migrations/0001_menu.up.sql

CREATE TABLE menu_items (
    id      BIGINT                              NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name    VARCHAR(50)                         NOT NULL UNIQUE,
    section ENUM('fish', 'sides', 'puddings')   NOT NULL,
    status  ENUM('available', 'sold_out')       NOT NULL DEFAULT 'available'
);

db/migrations/0001_menu.down.sql

DROP TABLE menu_items;

db/queries/menu.sql

-- sgl.query(AddItem, exec)
INSERT INTO menu_items (name, section) VALUES (?, ?);

-- sgl.query(SetStatus, execrows)
UPDATE menu_items SET status = ? WHERE name = ?;

-- sgl.query(ListMenu, many)
SELECT name, section, status FROM menu_items ORDER BY section, name;

-- sgl.query(ItemsInSection, many)
-- sgl.column(section, go_type: "seagull.sh/seagull/examples/mysql/007_enums/internal/menu.Section")
SELECT name, section, status FROM menu_items WHERE section = ? ORDER BY name;

example.go

package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"io"

	"seagull.sh/seagull/examples/mysql/007_enums/internal/menu"
	"seagull.sh/seagull/examples/mysql/007_enums/internal/store"
)

// run fills a chip shop menu whose section and status columns are MySQL ENUMs, marks an
// item sold out and reads the menu back. Each ENUM becomes a named Go type in the
// generated package unless an override names a type of your own:
//
//   - section has no override, so models.go declares store.MenuItemsSection, named after
//     the table and column that hold the ENUM, with a constant for each value, such as
//     store.MenuItemsSectionFish, and a Valid method; ListMenuRow.Section,
//     AddItemParams.Section and ItemsInSection's parameter are all a
//     store.MenuItemsSection;
//   - sgl.yaml's types.overrides names the column menu_items.status, so every query that
//     returns it as it is gives menu.Status, as in ListMenuRow.Status, and so does every
//     parameter compared with or assigned to it, as in SetStatusParams.Status; models.go
//     uses it too, and the generated package declares no type for that ENUM;
//   - sgl.column(section, go_type: ...) in ItemsInSection gives that one query's returned
//     Section the type menu.Section; the parameter it compares with stays a
//     store.MenuItemsSection.
//
// An ENUM sorts in the order its values are declared, not alphabetically, so ORDER BY
// section lists fish, then sides, then puddings. Valid reports whether a value is one the
// ENUM declares, so the shop can check a section before it binds it. In strict mode, the
// default since MySQL 5.7, a value the ENUM does not list is refused as it is stored as
// well; MySQL keeps an ENUM as the index of its value, and its error says the value was
// truncated.
//
// Takes database (*sql.DB) which is a migrated database.
// Takes output (io.Writer) which receives the menu, the fish section and the refusal.
//
// Returns error when a query fails other than as the example expects.
func run(ctx context.Context, database *sql.DB, output io.Writer) error {
	queries := store.New(database)

	items := []store.AddItemParams{
		{Name: "cod", Section: store.MenuItemsSectionFish},
		{Name: "haddock", Section: store.MenuItemsSectionFish},
		{Name: "chips", Section: store.MenuItemsSectionSides},
		{Name: "mushy peas", Section: store.MenuItemsSectionSides},
		{Name: "sticky toffee pudding", Section: store.MenuItemsSectionPuddings},
	}
	for _, item := range items {
		if err := queries.AddItem(ctx, item); err != nil {
			return fmt.Errorf("adding %s: %w", item.Name, err)
		}
	}
	if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: menu.SoldOut, Name: "haddock"}); err != nil {
		return fmt.Errorf("selling out of haddock: %w", err)
	}

	listed, err := queries.ListMenu(ctx)
	if err != nil {
		return fmt.Errorf("listing the menu: %w", err)
	}
	var menuRow store.ListMenuRow
	fmt.Fprintf(output, "ListMenu: section %T, status %T\n", menuRow.Section, menuRow.Status)
	for _, item := range listed {
		fmt.Fprintf(output, "  %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))
	}

	fish, err := queries.ItemsInSection(ctx, store.MenuItemsSectionFish)
	if err != nil {
		return fmt.Errorf("listing the fish: %w", err)
	}
	var sectionRow store.ItemsInSectionRow
	fmt.Fprintf(output, "ItemsInSection: section %T, status %T\n", sectionRow.Section, sectionRow.Status)
	for _, item := range fish {
		fmt.Fprintf(output, "  %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))
	}

	drinks := store.MenuItemsSection("drinks")
	fmt.Fprintf(output, "section drinks is valid: %t\n", drinks.Valid())
	err = queries.AddItem(ctx, store.AddItemParams{Name: "lemonade", Section: drinks})
	if err == nil {
		return errors.New("added an item to a section the enum does not list")
	}
	fmt.Fprintf(output, "refused section drinks: %v\n", err)
	return nil
}

// soldOut returns a note for an item that has sold out, comparing against the typed
// constant rather than a string.
//
// Takes status (menu.Status) which is the item's status.
//
// Returns string which is the note, or empty when the item is available.
func soldOut(status menu.Status) string {
	if status == menu.SoldOut {
		return " (sold out)"
	}
	return ""
}

internal/menu/menu.go

// Package menu holds the chip shop's own types. The generated store uses them in place of
// the enum types it would declare, through the override in sgl.yaml and the queries.
package menu

// Section is the part of the menu an item is listed in, matching the section ENUM.
type Section string

const (
	// Fish lists the fried fish.
	Fish Section = "fish"

	// Sides lists chips, peas and sauces.
	Sides Section = "sides"

	// Puddings lists the desserts.
	Puddings Section = "puddings"
)

// Status is whether a menu item can be ordered, matching the status ENUM.
type Status string

const (
	// Available marks an item that can be ordered.
	Available Status = "available"

	// SoldOut marks an item that has run out for the day.
	SoldOut Status = "sold_out"
)

main.go

// Command 007_enums applies its migrations to a MySQL database, then runs the example in
// example.go: a chip shop menu with two ENUM columns is read back as the enum types
// Seagull generates and as the shop's own Go types.
//
// Run it with `go run . -dsn 'user:password@tcp(localhost:3306)/menu'`, or set
// DATABASE_URL instead of passing -dsn. Give it an empty database of its own, as the
// migrations create its tables and the example adds rows each time it runs.
package main

import (
	"context"
	"database/sql"
	"embed"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/go-sql-driver/mysql"

	"seagull.sh/seagull/sdk/migrate"
	"seagull.sh/seagull/sdk/migrate/migrate_backend_mysql"
)

// migrations holds the migration files, compiled into the binary.
//
//go:embed db/migrations
var migrations embed.FS

func main() {
	dsn := flag.String("dsn", os.Getenv("DATABASE_URL"), "MySQL data source name; defaults to $DATABASE_URL")
	flag.Parse()
	if err := execute(context.Background(), *dsn); err != nil {
		log.Fatal(err)
	}
}

// execute opens and migrates the database dsn names, and runs the example against it.
//
// Takes dsn (string) which is the database's data source name.
//
// Returns error when the database cannot be prepared or the example fails.
func execute(ctx context.Context, dsn string) error {
	if dsn == "" {
		return errors.New("set -dsn or DATABASE_URL to a MySQL data source name")
	}
	config, err := mysql.ParseDSN(dsn)
	if err != nil {
		return err
	}
	// Seagull maps DATETIME and TIMESTAMP columns to time.Time, which the driver returns
	// only when parseTime is set.
	config.ParseTime = true
	dsn = config.FormatDSN()

	database, err := sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	defer func() { _ = database.Close() }()

	migrator, err := migrate.NewMigrator(database, migrate_backend_mysql.New(migrate.WithDriverMultiStatements(config.MultiStatements)), migrations, "db/migrations")
	if err != nil {
		return err
	}
	if _, err := migrator.Up(ctx); err != nil {
		return fmt.Errorf("migrating: %w", err)
	}
	return run(ctx, database, os.Stdout)
}

internal/store/menu.sql.go

// Code generated by sgl. DO NOT EDIT.

package store

import (
	"context"

	"seagull.sh/seagull/examples/mysql/007_enums/internal/menu"
)

const addItemSQL = `INSERT INTO menu_items (name, section) VALUES (?, ?);`

type AddItemParams struct {
	Name    string
	Section MenuItemsSection
}

func (queries *Queries) AddItem(ctx context.Context, params AddItemParams) error {
	_, err := queries.writer.ExecContext(ctx, addItemSQL, params.Name, params.Section)
	return err
}

const itemsInSectionSQL = `SELECT name, section, status FROM menu_items WHERE section = ? ORDER BY name;`

type ItemsInSectionRow struct {
	Name    string       `json:"name"`
	Section menu.Section `json:"section"`
	Status  menu.Status  `json:"status"`
}

func (queries *Queries) ItemsInSection(ctx context.Context, section MenuItemsSection) ([]ItemsInSectionRow, error) {
	rows, err := queries.reader.QueryContext(ctx, itemsInSectionSQL, section)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var results []ItemsInSectionRow
	for rows.Next() {
		var row ItemsInSectionRow
		if err := rows.Scan(&row.Name, &row.Section, &row.Status); err != nil {
			return nil, err
		}
		results = append(results, row)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return results, nil
}

const listMenuSQL = `SELECT name, section, status FROM menu_items ORDER BY section, name;`

type ListMenuRow struct {
	Name    string           `json:"name"`
	Section MenuItemsSection `json:"section"`
	Status  menu.Status      `json:"status"`
}

func (queries *Queries) ListMenu(ctx context.Context) ([]ListMenuRow, error) {
	rows, err := queries.reader.QueryContext(ctx, listMenuSQL)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var results []ListMenuRow
	for rows.Next() {
		var row ListMenuRow
		if err := rows.Scan(&row.Name, &row.Section, &row.Status); err != nil {
			return nil, err
		}
		results = append(results, row)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return results, nil
}

const setStatusSQL = `UPDATE menu_items SET status = ? WHERE name = ?;`

type SetStatusParams struct {
	Status menu.Status
	Name   string
}

func (queries *Queries) SetStatus(ctx context.Context, params SetStatusParams) (int64, error) {
	results, err := queries.writer.ExecContext(ctx, setStatusSQL, params.Status, params.Name)
	if err != nil {
		return 0, err
	}
	return results.RowsAffected()
}

internal/store/models.go

// Code generated by sgl. DO NOT EDIT.

package store

import "seagull.sh/seagull/examples/mysql/007_enums/internal/menu"

type MenuItems struct {
	ID      int64            `json:"id"`
	Name    string           `json:"name"`
	Section MenuItemsSection `json:"section"`
	Status  menu.Status      `json:"status"`
}

type MenuItemsSection string

const (
	MenuItemsSectionFish     MenuItemsSection = "fish"
	MenuItemsSectionSides    MenuItemsSection = "sides"
	MenuItemsSectionPuddings MenuItemsSection = "puddings"
)

// Valid reports whether the value is one the enum declares.
func (value MenuItemsSection) Valid() bool {
	switch value {
	case MenuItemsSectionFish, MenuItemsSectionSides, MenuItemsSectionPuddings:
		return true
	}
	return false
}

internal/store/querier.go

// Code generated by sgl. DO NOT EDIT.

// Package store reads DATETIME, DATE and TIMESTAMP columns into time.Time, so the DSN it connects with must set parseTime=true.
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()
}

output.txt

ListMenu: section store.MenuItemsSection, status menu.Status
  fish      cod
  fish      haddock (sold out)
  sides     chips
  sides     mushy peas
  puddings  sticky toffee pudding
ItemsInSection: section menu.Section, status menu.Status
  fish      cod
  fish      haddock (sold out)
section drinks is valid: false
refused section drinks: Error 1265 (01000): Data truncated for column 'section' at row 1

Also on other engines