probo / rules
getprobo/probo/.cursor/rules/go-upsert-returning-id.mdc
Upsert must RETURNING full row into pointer receiver, not use xmax
Cursor rule1.4k starsChanged 3 months ago
What's in it
- Upsert: RETURNING full row into receiver
---
description: Upsert must RETURNING full row into pointer receiver, not use xmax
globs: "pkg/coredata/**/*.go"
alwaysApply: false
---
# Upsert: RETURNING full row into receiver
Upsert methods use a **pointer receiver** and `RETURNING` all struct columns to sync the receiver with the actual DB state. Save the original ID before the query; compare it with the returned ID to detect insert vs update.
Do **not** use `RETURNING (xmax = 0) AS inserted` — `xmax` is a PostgreSQL internal system column and is fragile.
```go
// GOOD — RETURNING full row, sync receiver
func (t *Thing) Upsert(ctx context.Context, conn pg.Tx) (inserted bool, err error) {
q := `
INSERT INTO things (id, name, created_at, updated_at)
VALUES (@id, @name, @created_at, @updated_at)
ON CONFLICT (name) DO UPDATE
SET
name = EXCLUDED.name,
updated_at = EXCLUDED.updated_at
RETURNING
id,
name,
created_at,
updated_at
`
originalID := t.ID
// ...args...
rows, err := conn.Query(ctx, q, args)
if err != nil {
return false, fmt.Errorf("cannot upsert thing: %w", err)
}
defer rows.Close()
row, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Thing])
if err != nil {
return false, fmt.Errorf("cannot collect upsert result: %w", err)
}
*t = row
return originalID == t.ID, nil
}
// BAD — xmax trick
RETURNING (xmax = 0) AS inserted
// BAD — RETURNING only id without syncing receiver
RETURNING id
```
More agent context in getprobo/probo
29 other files this repository gives its agents.
AGENTS.md
CLAUDE.md
Cursor rule
- .cursor/rules/code-comments.mdc
- .cursor/rules/config-propagation.mdc
- .cursor/rules/coredata-migrations.mdc
- .cursor/rules/detail-page-back-link.mdc
- .cursor/rules/git-commit-signing.mdc
- .cursor/rules/git-commit-style.mdc
- .cursor/rules/go-authorize-scope.mdc
- .cursor/rules/go-coredata-load-naming.mdc
- .cursor/rules/go-declarations.mdc
- .cursor/rules/go-delete-no-rows-check.mdc
- .cursor/rules/go-error-handling.mdc
- .cursor/rules/go-imports.mdc
- .cursor/rules/go-logging.mdc
- .cursor/rules/go-multiline-params.mdc
- .cursor/rules/go-naming-conventions.mdc
- .cursor/rules/go-pg-constraint-check.mdc
- .cursor/rules/go-url-construction.mdc
- .cursor/rules/list-filtering.mdc
- .cursor/rules/no-outlet-context-data.mdc
- .cursor/rules/prompt-style.mdc
- .cursor/rules/react-named-exports-lazy-entry.mdc
- .cursor/rules/relay-connection-item-components.mdc
- .cursor/rules/relay-fragments-not-data-props.mdc
- .cursor/rules/relay-required-directive.mdc
- .cursor/rules/skeleton-width-sync.mdc
- .cursor/rules/template-files.mdc
- .cursor/rules/v2-color-scale.mdc
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

