# Bob SQL Executor — running queries & scanning (cross-cutting) The executor builds a query, runs it, and scans rows into Go values in one step. It's built on `github.com/stephenafamo/scan`; the scanning functions (`bob.One`, `bob.All`, …) are thin wrappers over `scan.One`/`scan.All`/etc. ## Getting an Executor `bob.Executor` is `scan.Queryer` (a `QueryContext` that returns `scan.Rows`) plus `ExecContext`. A plain `*sql.DB` does **not** satisfy it directly (its `QueryContext` returns `*sql.Rows`), so wrap it: ```go import "github.com/stephenafamo/bob" db := bob.NewDB(sqlDB) // wrap an existing *sql.DB -> bob.DB (embeds *sql.DB) db, err := bob.Open("pgx", dsn) // or open directly (wraps sql.Open) // bob.OpenDB(connector), bob.NewConn(*sql.Conn) also exist. ``` `bob.Open`/`sql.Open` need the `database/sql` driver registered by a blank import — same as plain `database/sql`. For Postgres that's `_ "github.com/lib/pq"` (driver name `"postgres"`) or `_ "github.com/jackc/pgx/v5/stdlib"` (driver name `"pgx"`); for SQLite, `_ "modernc.org/sqlite"`. `bob.DB` embeds `*sql.DB`, so all standard methods remain. Pass `db` (or a `bob.Tx`) as the `Executor` to any scanning function. Returning `scan.Rows` (an interface) rather than `*sql.Rows` is what makes executors mockable in tests. ## Transactions ```go tx, err := db.BeginTx(ctx, nil) // bob.Tx (embeds *sql.Tx) // ... use tx as the Executor ... err = tx.Commit(ctx) // or tx.Rollback(ctx) // Or the managed helper — commits on nil error, rolls back on error/panic: err = db.RunInTx(ctx, nil, func(ctx context.Context, exec bob.Executor) error { _, err := bob.Exec(ctx, exec, psql.Insert(/* ... */)) return err }) ``` ## Mappers A scanning function needs a `scan.Mapper[T]` that maps result columns to `T`: - `scan.StructMapper[T]()` — maps columns to struct fields (by `db` tag / name). The default for rows. - Single-column results map to primitives directly (e.g. `scan.SingleColumnMapper[int]`). ```go import "github.com/stephenafamo/scan" type User struct { ID int `db:"id"` Name string `db:"name"` } mapper := scan.StructMapper[User]() ``` ## Scanning functions (package `bob`) All take `(ctx, exec Executor, q Query, m scan.Mapper[T])` unless noted. `q` is any built query (Layer 1 builder, or `models.Jets.Query(...)` — generated queries also expose their own `.One/.All`). | Function | Returns | Use for | |----------|---------|---------| | `bob.Exec(ctx, exec, q)` | `(sql.Result, error)` | INSERT/UPDATE/DELETE with no rows back (no mapper) | | `bob.One(..., m)` | `(T, error)` | exactly one row (`sql.ErrNoRows` if none) | | `bob.All(..., m)` | `([]T, error)` | all rows into a slice | | `bob.Allx[Tr](..., m)` | `(V, error)` | all rows into a **custom** slice type via a transformer | | `bob.Cursor(..., m)` | `(scan.ICursor[T], error)` | stream row-by-row; `Next()/Get()/Close()` | | `bob.Each(..., m)` | range-over-func | stream via a Go 1.23 `for ... range` iterator | ```go q := psql.Select(sm.From("users"), sm.Where(psql.Quote("id").EQ(psql.Arg(1)))) user, err := bob.One(ctx, db, q, scan.StructMapper[User]()) users, err := bob.All(ctx, db, q, scan.StructMapper[User]()) // Stream a large result set without loading it all: for user, err := range bob.Each(ctx, db, q, scan.StructMapper[User]()) { if err != nil { /* ... */ } // use user } // Or a cursor: c, err := bob.Cursor(ctx, db, q, scan.StructMapper[User]()) defer c.Close() for c.Next() { user, err := c.Get() _ = user; _ = err } // Custom slice type: type Users []User us, err := bob.Allx[bob.SliceTransformer[User, Users]](ctx, db, q, scan.StructMapper[User]()) ``` ## Prepared statements `bob.Prepare` (exec-only) and `bob.PrepareQuery` (returns rows) build a reusable statement. `Arg` is the type of the bound argument bundle: ```go // Query statement: stmt, err := bob.PrepareQuery(ctx, db, q, scan.StructMapper[User]()) defer stmt.Close() users, err := stmt.All(ctx) // also .One(ctx), .Cursor(ctx) // Exec statement: estmt, err := bob.Prepare(ctx, db, psql.Update(/* ... */)) _, err = estmt.Exec(ctx) ``` `PrepareQueryx` returns a custom slice type, mirroring `Allx`. ## Two-step alternative (no executor) If you'd rather run the query with plain `database/sql`, build the string yourself and execute it: ```go q, args, err := psql.Insert(im.Into("films"), im.Values(psql.Arg("UA502"))).Build(ctx) _, err = sqlDB.ExecContext(ctx, q, args...) ``` This cooperates fully with anything that takes a query string + args (pgx, sqlx, your own pool wrapper) — you only need the executor when you want Bob to do the scanning.