--- 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`.