Quarry Docs
GitHub
Example

Admin Edit Form

This is the pattern for a back-office form: several optional fields, a few values that can be intentionally cleared, and a returning clause so the UI can refresh from the database instead of guessing.

Quarry logo

The form payload

type AdminUserForm struct {
    ID          int
    DisplayName *string
    Email       *string
    Bio         *string
    Enabled     *bool
    TeamID      *int
}

The important bit is that the form uses pointers for fields that may be omitted. A nil pointer means “leave it alone.” A non-nil pointer means “the user actually submitted something.”

Apply the patch

func UpdateUser(ctx context.Context, db scan.Queryer, qq *quarry.Quarry, form AdminUserForm) (*AdminUserRow, error) {
    q := qq.Update("users").
        SetOptional("display_name", form.DisplayName).
        SetOptional("email", form.Email).
        SetOptional("bio", form.Bio).
        SetIf(form.Enabled != nil, "enabled", *form.Enabled).
        SetIf(form.TeamID != nil, "team_id", *form.TeamID).
        Where(quarry.Eq("id", form.ID)).
        Returning("id", "display_name", "email", "bio", "enabled", "team_id")

    return scan.One[AdminUserRow](ctx, db, q)
}
Why this is better than manual branching The builder takes care of the SET list and the dialect-specific RETURNING clause. The handler only decides which fields are present.

When a field must be clearable

If the UI needs to clear a value, send an explicit nil-or-empty signal. In this sample, DisplayName and Bio can be cleared by sending an empty string through a pointer field and handling that logic before the query builder runs.

func normalizeForm(form *AdminUserForm) {
    if form.DisplayName != nil && *form.DisplayName == "" {
        empty := ""
        form.DisplayName = &empty
    }
}

The point is not to compress everything into one helper. The point is to keep the SQL assembly boring and move intent handling into the request layer where it belongs.

Map-driven admin toggles

q := qq.Update("users").
    SetMap(map[string]any{
        "role":       "moderator",
        "locked":     false,
        "updated_at":  time.Now(),
    }).
    Where(quarry.Eq("id", form.ID)).
    Returning("id", "role", "locked")

SetMap is handy when a form or admin tool already has stable keys and you want the update builder to keep the SET order deterministic.

What the SQL looks like

UPDATE users
SET display_name = $1, email = $2, enabled = $3, team_id = $4
WHERE id = $5
RETURNING id, display_name, email, bio, enabled, team_id
Dialect note The same handler can run on Postgres and SQLite. MySQL will still render the update, but the RETURNING clause will fail clearly because the dialect does not support it.