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>
127 lines
6.8 KiB
Markdown
127 lines
6.8 KiB
Markdown
---
|
|
name: golang-bob
|
|
description: 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:
|
|
|
|
1. **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).
|
|
2. **Convenience, not magic** — no hidden abstraction; queries map to the SQL you expect.
|
|
3. **Cooperation** — built on `database/sql` and `github.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 show `dialect/psql/insert/im` — that nesting does **not**
|
|
exist. (`sm`=select, `im`=insert, `um`=update, `dm`=delete mods; also `fm` function, `wm` window,
|
|
`vm` view, `mm` merge.)
|
|
- **`Arg()` makes a placeholder; a bare string is literal SQL.** `psql.Quote("id").EQ(psql.Arg(5))`
|
|
parameterizes `5`. Passing a Go value directly as a literal interpolates it as text — only `Arg`
|
|
(or `ArgGroup`) 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 pass `sm.From(psql.Quote("users"))`. Some
|
|
string params (CTE names, `SetCol`, join `Using`) auto-quote; see `query-builder.md`.
|
|
- **`Raw` uses `?` 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`, not `Add`.** The hooks doc page shows `.Add(...)`; the real
|
|
method is `table.BeforeInsertHooks.AppendHooks(fn)`. Skip hooks per-call with `bob.SkipHooks(ctx)`.
|
|
|
|
## Quick reference
|
|
|
|
**Dialect + mod packages** (swap `psql` for `mysql`/`sqlite`):
|
|
|
|
```go
|
|
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):
|
|
|
|
```go
|
|
// 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`):
|
|
|
|
```go
|
|
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`.
|