Reference skill for github.com/stephenafamo/bob (v0.46.0): query builder, generated models/ORM, bobgen code generation, factories, and result scanning. SKILL.md routes to per-subsystem reference files (query-builder, models, code-generation, execution, comparisons). Symlinked into ~/.claude/skills and ~/.agents/skills (Codex). Verified with a RED/GREEN subagent test and cross-checked against v0.46.0 source (flat mod import paths, AppendHooks, plural table var, WhereMod ops). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.8 KiB
| name | description |
|---|---|
| golang-bob | Use when writing Go against the Bob SQL toolkit (github.com/stephenafamo/bob) — building dialect SQL with query mods (sm/im/um/dm), generated models and setters, bobgen code generation, test factories, typed queries from .sql files, or scanning rows with bob.One/All/Cursor. Covers PostgreSQL, MySQL, and SQLite. |
Bob — Go SQL Access Toolkit
Overview
Bob (github.com/stephenafamo/bob) is a Go SQL toolkit by Stephen Afamo — the author of
SQLBoiler, who started Bob as "an experiment for how v5 of SQLBoiler could look." It is
database-first and spans a fluent query builder up to a fully generated, type-safe ORM
with test factories. This skill targets v0.46.0.
Three principles drive every design decision:
- Correctness — each dialect is hand-crafted to match its SQL spec; you cannot build a query the dialect doesn't support (it won't compile).
- Convenience, not magic — no hidden abstraction; queries map to the SQL you expect.
- Cooperation — built on
database/sqlandgithub.com/stephenafamo/scan, not around them.
Progressive adoption is the whole point. Use one layer or all four:
| Layer | What you get | Analogous to | Type-safe |
|---|---|---|---|
| 1. Query builder | Fluent, dialect-specific SQL builder (no DB knowledge) | squirrel | No (string-based) |
| 2. Models + ORM gen | Generated structs, CRUD, typed WHERE/JOIN mods, relationships, eager loading | SQLBoiler | Yes |
| 3. Factory gen | Generated test fixtures that auto-create required relations | Ruby FactoryBot | Yes |
| 4. Query gen | Type-safe Go funcs from hand-written .sql files |
sqlc | Yes |
Cutting across all layers, the SQL executor (bob.One/All/Cursor/Each/Exec) builds and runs
a query and scans rows into structs in one step.
All three of PostgreSQL, MySQL/MariaDB and SQLite support every layer.
When to use Bob (and when not)
Reach for Bob when you want database-first type safety without writing scan boilerplate;
you need to build arbitrary dialect-specific SQL (window functions, CTEs, DISTINCT ON,
FOR UPDATE SKIP LOCKED, upserts) that a lowest-common-denominator builder can't express;
or you want generated factories for relationship-heavy tests.
The query builder alone (Layer 1) is a fine, dependency-light replacement for squirrel even if you never generate models.
Consider alternatives when: you want code-first/migrations-owned schema (Ent, GORM); you
only ever write raw SQL and just need typed funcs (plain sqlc); or you don't want a DB to exist
before generating code (Bob introspects a live DB or .sql schema files — it does not manage
migrations). See comparisons.md for a faithful Bob-vs-{GORM,Ent,SQLBoiler,Jet} table.
Deliberately NOT in Bob: automatic created_at/updated_at timestamps and soft-deletes.
Do timestamps at the DB level (defaults/triggers); soft-deletes are left to you.
Critical conventions (read before writing code)
These are the things that trip people up — verified against v0.46.0 source:
- Queries are built from "query mods."
psql.Select(sm.From("users"), sm.Where(...))— every argument is a mod. Each query type in each dialect has its own mod package, so wrong mods fail at compile time. - Import paths are flat per dialect:
dialect/psql,dialect/psql/sm,.../im,.../um,.../dm. Older docs/examples sometimes showdialect/psql/insert/im— that nesting does not exist. (sm=select,im=insert,um=update,dm=delete mods; alsofmfunction,wmwindow,vmview,mmmerge.) Arg()makes a placeholder; a bare string is literal SQL.psql.Quote("id").EQ(psql.Arg(5))parameterizes5. Passing a Go value directly as a literal interpolates it as text — onlyArg(orArgGroup) produces a bound parameter. This is the #1 injection footgun.- Bare strings in
any-typed mod args are emitted verbatim, NOT quoted.sm.From("users")→FROM users(unquoted). For a quoted identifier passsm.From(psql.Quote("users")). Some string params (CTE names,SetCol, joinUsing) auto-quote; seequery-builder.md. Rawuses?placeholders in every dialect.psql.Raw("id = ?", 5)— Bob rewrites?to$1/?1/?per dialect on build. Escape a literal?as\?..Apply()mutates the query in place. To reuse a base query,.Clone()first.- Hooks register with
AppendHooks, notAdd. The hooks doc page shows.Add(...); the real method istable.BeforeInsertHooks.AppendHooks(fn). Skip hooks per-call withbob.SkipHooks(ctx).
Quick reference
Dialect + mod packages (swap psql for mysql/sqlite):
import (
"github.com/stephenafamo/bob"
"github.com/stephenafamo/bob/dialect/psql" // Select/Insert/Update/Delete + starters (Arg, Quote, F, S, And...)
"github.com/stephenafamo/bob/dialect/psql/sm" // SELECT mods (From, Where, Join, GroupBy, Limit...)
"github.com/stephenafamo/bob/dialect/psql/im" // INSERT mods (Into, Values, OnConflict...)
"github.com/stephenafamo/bob/dialect/psql/um" // UPDATE mods (Table, SetCol, Where...)
"github.com/stephenafamo/bob/dialect/psql/dm" // DELETE mods (From, Where, Using...)
)
Build → run (two styles):
// A) Build the string yourself, run with database/sql
q, args, err := psql.Select(sm.From("users"), sm.Where(psql.Quote("id").EQ(psql.Arg(1)))).Build(ctx)
rows, err := db.QueryContext(ctx, q, args...)
// B) Build + run + scan in one step with the executor
exec := bob.NewDB(sqlDB) // wrap *sql.DB
user, err := bob.One(ctx, exec, q, scan.StructMapper[User]()) // -> User
users, err := bob.All(ctx, exec, q, scan.StructMapper[User]()) // -> []User
Generated-model usage (after bobgen; table jets → models.Jets):
jet, err := models.FindJet(ctx, db, 10) // by PK
jets, err := models.Jets.Query(models.SelectWhere.Jets.ID.EQ(100)).All(ctx, db)
jet, err = models.Jets.Insert(&models.JetSetter{Name: omit.From("x")}).One(ctx, db)
Where to look next
Load the reference file for the layer you're working in — each has verbatim, dialect-correct examples:
| You are... | Read |
|---|---|
| Building SQL by hand (any layer 1 work, mods, expressions, dialect differences) | query-builder.md |
| Using generated models — CRUD, setters, typed WHERE/JOIN, relationships, eager loading, hooks | models.md |
Setting up bobgen — config, drivers, typed .sql queries, factories, enums, relationships config |
code-generation.md |
Running queries — executor, One/All/Each/Cursor, transactions, prepared statements, scanning |
execution.md |
| Choosing Bob vs GORM/Ent/SQLBoiler/Jet | comparisons.md |
Hand-written ORM models (without codegen) and the orm.NewTable/NewView primitives are covered
at the end of models.md.